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.
- Create a Hyperlambda endpoint
- Invoke your own "extended" business logic in your Hyperlambda endpoint
- Invoke the "overridden" HTTP endpoint, optionally with additional data resulting from executing your Hyperlambda
- 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 ...? :D
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 :D
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": "johnny@hotmail.com"
}
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"
}
Top comments (8)
There is more than one way to be a good dev.
I'm a big proponent of static types because I'm lazy
Being lazy, I'm more than ready to accept the initial overhead to design and polish my static types well.
I do it because it means that I know I can rely on JetBrains IntelliJ and its refactoring magic to do so much of the tedious work afterwards.
Interestingly, static typing is why this is impractical to do with traditional programming languages. If I search for "How to create a web API using C#", I will find 1,000,000 hits on Google. 999,998 of these will tell me I need to create a "View Model" first, so I create a View Model.
If I was to implement the intercepter using static typing, I'd have to edit it every single time I do a change in my original API. With a dynamic language, such as Hyperlambda I don't have to edit it as long as the fields I am using in it doesn't change, allowing me to create it once, for then to mostly ignore it for the rest of its lifespan - Paradoxically allowing me to "continue being lazy" ... ;)
Because a dynamic API without strong typing simply "forwards whatever" I am feeding it, since there is no typing in it, and it accepts "whatever" ...
FYI, Hyperlambda is the "super lazy framework" arguably. Watch this where I'm creating some 25,000 lines of code, a complete CRM, creating it from scratch, and deploying it into production in 20 minutes to understand ...
My guess is most others, regardless of programming language or platform, would easily need weeks if not months to reproduce what I'm doing in the above video. If you're serious about "being lazy", the first thing you need to drop is static typing ...
If you're serious about "being lazy", there is simply no way you can beat this thing. This was created from scratch in some roughly 20 minutes ... ^_^
Hey, I will have a look at the video sometimes later, thanks.
I want to join you
Great! Go for it :)
How can I join you
Learn Hyperlambda, then let's talk ... :)
Maybe of interest to you guys @coco98 ...?