Aista – Magic Cloud – Generate a CRUD app in seconds

Categories
Hyperlambda

HTTP based OOP

This is probably going to sound too incredible to be true, but I think I just invented HTTP based OOP, allowing me to override HTTP endpoints.

There is a reason why I don’t like classic OOP. The primary one being that it destroys my creativity and ability to express myself. As proof of that, realise I just invented HTTP based OOP, with inheritance and polymorphism, with the capability of “overriding” an existing HTTP endpoint, and inject additional logic in my “HTTP interceptor”, allowing me to “extend” the original HTTP invocation. This allows me to inject additional custom business logic in my original HTTP endpoint, without modifying it. And of course, it’s almost impossible to implement using classic OOP.

In the video below I am demonstrating how to implement Stripe payments into “whatever” HTTP API you have from before, using polymorphism, and “overriding” my original HTTP invocation. Basically, I am demonstrating HTTP based OOP, overriding my existing endpoint.

The beauty of this is that it doesn’t matter what your original HTTP endpoint is implemented in, as long as it accepts JSON and returns JSON. You can use this with …

  • Python
  • PHP
  • GraphQL
  • PostgREST
  • Hasura
  • Supabase
  • C#
  • Java
  • “Whatever” really …

More interestingly, this is almost impossible to achieve with classic OOP, due to OOP’s “obsession with strongly typing”. You can literally only do this in a non-OOP context, where you completely ignore strongly typing – Hyperlambda being one such example. Implying that Hyperlambda can basically “override” anything you might have from before, as long as it’s based upon HTTP and JSON. The technique is easily understood by anyone understanding the concept of YALOA or “Yet Another Layer Of Abstraction”.

In the video example above, I need to apply some (tiny) changes to my original backend, to allow for associating users with Stripe customer IDs, create a one to many relationship between users and payment methods, and store payments and subscriptions internally in my app. However, these changes would be microscopic in nature compared to the code required to implement Stripe manually in my own backend. Hence, I basically eliminate 90% of the burden required to implement Stripe. And, if I want to, I can probably create a generalised version of the above example, where I am no longer dependent upon Stripe, but can easily exchange my payment provider with any other payment provider by simply changing my Hyperlambda file. The last part allows me to change my payment provider, without touching my own backend, but instead simply providing an additional “overridden” HTTP method.

Notice, if you want to ensure your intercepted HTTP endpoint becomes the equivalent of “private”, you might want to attach some “secret” token to the invocations towards your encapsulated endpoint, and only exposing it to your Magic cloudlet. If you don’t do this, people capable of “guessing” the URL to your original endpoint might in theory be able to fake payments, getting product for free.

The process is quite simple.

  1. Create a Hyperlambda endpoint
  2. Invoke your own “extended” business logic in your Hyperlambda endpoint
  3. Invoke the “overridden” HTTP endpoint, optionally with additional data resulting from executing your Hyperlambda
  4. Return to the client whatever data your “encapsulated” endpoint returns

Paradoxically, HTTP based OOP is not only good OOP, but also good SOLID, and for the most parts obeys by the Open-Closed Principle, and there is zero OOP in it. To explain it a bit humorously with some “geek humour” …

No classes where harmed while inventing HTTP OOP

Watch the above video to understand the concept. Now as to what to refer to this as? I’ve got no idea, however my initial intuition tells me it is O2, as in Objects to the second exponent or something – Suggestions …? 😀

At least that name would make Bjarne Stroustrup and Anders Hejlsberg choke for a while on their morning coffee … 😉

I want to emphasise that this is (almost) 100% impossible using classic OOP, and requires a super dynamic programming language such as Hyperlambda.

To reproduce what I am doing, register for a cloudlet below, and start playing with O2 …

Thank you for reading, now let the debate begin 😀

Below is my code …

echo.post.hl

add:x:+
   get-nodes:x:@.arguments/*
return

customer.post.hl

.arguments:*
.description:Interceptor invoking Stripe to create a Stripe customer, for then to attach the customer ID to specified payload, before invoking intercepted endpoint.

/*
 * Invoking Stripe to create a customer for then to attach
 * the customer ID to the payload we're passing in to the
 * original endpoint, before invoking intercepted endpoint
 * now with a Stripe Customer ID, allowing you to associate
 * the user internally with a Stripe customer object.
 */
.before

   /*
    * If you're using automatic tax calculations, you'll
    * need to pass in the IP address of the client - At
    * which point you'll have to uncomment the line of
    * code below, and pass it into slot invocation.
    */
   request.headers.get:X-Real-IP

   // Sanity checking invocation.
   validators.mandatory:x:@.arguments/*/name
   validators.mandatory:x:@.arguments/*/email
   validators.email:x:@.arguments/*/email

   // Invoking Stripe API.
   unwrap:x:+/*
   signal:stripe.customers.create
      name:x:@.arguments/*/name
      email:x:@.arguments/*/email
      ip_address:x:@request.headers.get

   // Attaching Stripe's customer id to the payload.
   unwrap:x:+/*/*
   add:x:../*/http.post/*/payload
      .
         stripe_customer_id:x:@signal

// Evaluating Stripe lambda object.
eval:x:@.before

// Endpoint we're intercepting.
.endpoint:"https://tiger-polterguy.gb.aista.com/magic/modules/stripe-interceptor/echo"

/*
 * Checking if we've got an Authorization HTTP header,
 * at which point we forward it to the original HTTP endpoint.
 */
request.headers.get:Authorization
if
   not-null:x:@request.headers.get
   .lambda
      add:x:../*/http.post/*/headers
         .
            Authorization:x:@request.headers.get

// Forwarding arguments given to endpoint to intercepted endpoint.
add:x:../*/http.post/*/payload
   get-nodes:x:@.arguments/*

// Invoking the intercepted HTTP endpoint.
http.post:x:@.endpoint
   headers
      Content-Type:application/json
   convert:true
   payload

// Returning the intercepted endpoint's status code.
response.status.set:x:@http.post

// Returning response payload from intercepted endpoint to caller.
add:x:+
   get-nodes:x:@http.post/*/content/*
return

payment-method.post.hl

.arguments:*
.description:Interceptor invoking Stripe to create a payment method for the specified customer, for then to attach the payment method id and payment method data to the specified payload, before invoking intercepted endpoint.

/*
 * Invoking Stripe to create a payment method and associate
 * it with the specified customer, for then to attach the
 * payment data to the original endpoint, before we invoke
 * intercepted endpoint.
 */
.before

   // Sanity checking invocation.
   validators.mandatory:x:@.arguments/*/card_number
   validators.mandatory:x:@.arguments/*/card_exp_month
   validators.mandatory:x:@.arguments/*/card_exp_year
   validators.mandatory:x:@.arguments/*/card_cvs
   validators.mandatory:x:@.arguments/*/customer_id

   // Invoking Stripe to create a payment method.
   unwrap:x:+/*
   signal:stripe.payment_methods.create
      card_number:x:@.arguments/*/card_number
      card_exp_month:x:@.arguments/*/card_exp_month
      card_exp_year:x:@.arguments/*/card_exp_year
      card_cvs:x:@.arguments/*/card_cvs

   // Invoking Stripe to attach the payment method to the customer.
   unwrap:x:+/*
   signal:stripe.payment_methods.attach
      customer_id:x:@.arguments/*/customer_id
      payment_method:x:@.before/*/signal/[0,1]/*/id

   // Making sure we pass in 4 last digits of card to intercepted endpoint.
   strings.length:x:@.arguments/*/card_number
   math.subtract:x:-
      .:int:4
   strings.substring:x:@.arguments/*/card_number
      get-value:x:@math.subtract
      .:int:4

   /* 
    * Passing in brand, payment method id, and 4 last digits of card
    * to intercepted endpoint.
    */
   unwrap:x:+/*/*
   add:x:../*/http.post/*/payload
      .
         brand:x:@.before/*/signal/[0,1]/*/brand
         card:x:@strings.substring
         payment_method_id:x:@.before/*/signal/[0,1]/*/id

   // Removing card data.
   remove-nodes:x:@.arguments/*/card_number
   remove-nodes:x:@.arguments/*/card_exp_month
   remove-nodes:x:@.arguments/*/card_exp_year
   remove-nodes:x:@.arguments/*/card_cvs

// Evaluating [.before] lambda object.
eval:x:@.before

// Endpoint we're intercepting.
.endpoint:"https://tiger-polterguy.gb.aista.com/magic/modules/stripe-interceptor/echo"

/*
 * Checking if we've got an Authorization HTTP header,
 * at which point we forward it to the original HTTP endpoint.
 */
request.headers.get:Authorization
if
   not-null:x:@request.headers.get
   .lambda
      add:x:../*/http.post/*/headers
         .
            Authorization:x:@request.headers.get

// Forwarding arguments given to endpoint to intercepted endpoint.
add:x:../*/http.post/*/payload
   get-nodes:x:@.arguments/*

// Invoking the intercepted HTTP endpoint.
http.post:x:@.endpoint
   headers
      Content-Type:application/json
   convert:true
   payload

// Returning the intercepted endpoint's status code.
response.status.set:x:@http.post

// Returning response payload from intercepted endpoint to caller.
add:x:+
   get-nodes:x:@http.post/*/content/*
return

payment.post.hl

.arguments:*
.description:Interceptor invoking Stripe to create a payment for a customer, for then to attach the payment data to specified payload, before invoking intercepted endpoint.

/*
 * Invoking Stripe to create a payment and associate
 * it with the customer, for then to attach the
 * payment data to the payload we're passing in to
 * the original endpoint.
 */
.before

   validators.mandatory:x:@.arguments/*/amount
   validators.mandatory:x:@.arguments/*/currency
   validators.mandatory:x:@.arguments/*/payment_method
   validators.mandatory:x:@.arguments/*/customer_id

   unwrap:x:+/*
   signal:stripe.payments.create
      amount:x:@.arguments/*/amount
      currency:x:@.arguments/*/currency
      payment_method:x:@.arguments/*/payment_method
      customer_id:x:@.arguments/*/customer_id

   // Passing in payment data to intercepted endpoint.
   unwrap:x:+/*/*
   add:x:../*/http.post/*/payload
      .
         payment_id:x:@signal/*/id

// Evaluating [.before] lambda object.
eval:x:@.before

// Endpoint we're intercepting.
.endpoint:"https://tiger-polterguy.gb.aista.com/magic/modules/stripe-interceptor/echo"

/*
 * Checking if we've got an Authorization HTTP header,
 * at which point we forward it to the original HTTP endpoint.
 */
request.headers.get:Authorization
if
   not-null:x:@request.headers.get
   .lambda
      add:x:../*/http.post/*/headers
         .
            Authorization:x:@request.headers.get

// Forwarding arguments given to endpoint to intercepted endpoint.
add:x:../*/http.post/*/payload
   get-nodes:x:@.arguments/*

// Invoking the intercepted HTTP endpoint.
http.post:x:@.endpoint
   headers
      Content-Type:application/json
   convert:true
   payload

// Returning the intercepted endpoint's status code.
response.status.set:x:@http.post

// Returning response payload from intercepted endpoint to caller.
add:x:+
   get-nodes:x:@http.post/*/content/*
return

subscription.delete.hl

.arguments:*
.description:Interceptor invoking Stripe to create a subscription for a customer, for then to attach the subscription id to specified payload, before invoking intercepted endpoint.

/*
 * Invoking Stripe to create a subscription and associate
 * it with the customer, for then to attach the
 * subscription data to the payload we're passing in to
 * the original endpoint.
 */
.before

   // Sanity checking invocation.
   validators.mandatory:x:@.arguments/*/subscription

   // Invoking Stripe.
   unwrap:x:+/*
   signal:stripe.subscriptions.cancel
      subscription:x:@.arguments/*/subscription

// Evaluating [.before] lambda object.
eval:x:@.before

// Endpoint we're intercepting.
.endpoint:"https://tiger-polterguy.gb.aista.com/magic/modules/stripe-interceptor/echo"

/*
 * Checking if we've got an Authorization HTTP header,
 * at which point we forward it to the original HTTP endpoint.
 */
request.headers.get:Authorization
if
   not-null:x:@request.headers.get
   .lambda
      add:x:../*/http.post/*/headers
         .
            Authorization:x:@request.headers.get

// Forwarding arguments given to endpoint to intercepted endpoint.
add:x:../*/http.post/*/payload
   get-nodes:x:@.arguments/*

// Invoking the intercepted HTTP endpoint.
http.post:x:@.endpoint
   headers
      Content-Type:application/json
   convert:true
   payload

// Returning the intercepted endpoint's status code.
response.status.set:x:@http.post

// Returning response payload from intercepted endpoint to caller.
add:x:+
   get-nodes:x:@http.post/*/content/*
return

subscription.post.hl

.arguments:*
.description:Interceptor invoking Stripe to create a subscription for a customer, for then to attach the subscription id to specified payload, before invoking intercepted endpoint.

/*
 * Invoking Stripe to create a subscription and associate
 * it with the customer, for then to attach the
 * subscription data to the payload we're passing in to
 * the original endpoint.
 */
.before

   // Sanity checking invocation.
   validators.mandatory:x:@.arguments/*/price
   validators.mandatory:x:@.arguments/*/customer_id
   validators.mandatory:x:@.arguments/*/payment_method

   // Invoking Stripe.
   unwrap:x:+/*
   signal:stripe.subscriptions.create
      price:x:@.arguments/*/price
      payment_method:x:@.arguments/*/payment_method
      customer_id:x:@.arguments/*/customer_id

   // Passing in subscription data to intercepted endpoint.
   unwrap:x:+/*/*
   add:x:../*/http.post/*/payload
      .
         subscription_id:x:@signal/*/id
         product:x:@signal/*/product

// Evaluating [.before] lambda object.
eval:x:@.before

// Endpoint we're intercepting.
.endpoint:"https://tiger-polterguy.gb.aista.com/magic/modules/stripe-interceptor/echo"

/*
 * Checking if we've got an Authorization HTTP header,
 * at which point we forward it to the original HTTP endpoint.
 */
request.headers.get:Authorization
if
   not-null:x:@request.headers.get
   .lambda
      add:x:../*/http.post/*/headers
         .
            Authorization:x:@request.headers.get

// Forwarding arguments given to endpoint to intercepted endpoint.
add:x:../*/http.post/*/payload
   get-nodes:x:@.arguments/*

// Invoking the intercepted HTTP endpoint.
http.post:x:@.endpoint
   headers
      Content-Type:application/json
   convert:true
   payload

// Returning the intercepted endpoint's status code.
response.status.set:x:@http.post

// Returning response payload from intercepted endpoint to caller.
add:x:+
   get-nodes:x:@http.post/*/content/*
return

JSON Payloads

POST customer

{
    "name": "Johnny Two Times",
    "email": "[email protected]"
}

POST payment-method

{
    "card_number": "4242424242424242",
    "card_exp_month": "10",
    "card_exp_year": "2023",
    "card_cvs": "314",
    "customer_id": "CUSTOMER_ID_FROM_PREVIOUS_INVOCATION"
}

POST payment

{
    "amount": 1234,
    "currency": "USD",
    "payment_method": "PAYMENT_METHOD_ID_FROM_PREVIOUS_INVOCATION",
    "customer_id": "CUSTOMER_ID_FROM_PREVIOUS_INVOCATION"
}

POST subscription

{
    "price": "PRICE_FROM_STRIPE_DASHBOARD",
    "customer_id": "CUSTOMER_ID_FROM_PREVIOUS_INVOCATION",
    "payment_method": "PAYMENT_METHOD_ID_FROM_PREVIOUS_INVOCATION"
}