And reduce the size of your future PRs with the Open/Closed Principle
Suppose I use an external service provider to process payments for my E-commerce app, and I need to embed some external SDK code to integrate the payment service into my app.
In this oversimplified example, let’s say the payment service is responsible for checking whether a given payment method (e.g, Apple Pay and Google Pay) is available based on the customer’s device, region, etc. While my “core” UI component PaymentOptions
is responsible for rendering the available payment methods as options. Lastly, I want the flexibility of adding new payment methods in the future (for 📈💰reasons).
I can write it this way.
However, the UI code is tightly coupled with the external code from the payment service, i.e., I have to modify the PaymentOptions
component in order to add a new payment method or to make SDK updates.
I can perhaps break out the SDK code into a separate hook.
However, I still have to modify PaymentOptions
and any other components that share the usePaymentMethods
hook if I wanted to add, for example, isPaypalAvailable
.
To minimize the size of future PRs, I’ve been thinking about the Open/Closed Principle, the “O” in SOLID (check out this excellent explainer): “A software artifact should be open for extension but closed for modification.”
In my own words: I should design this feature in such a way that I don’t have to touch any of the original code I wrote (closed for modification) if I were to add new payment methods in the future (open for extension).
Here’s my take on this principle. Let’s separate the payment service into its own module: a simple object where each key represents a payment method. Every payment method key points to an object with an isAvailable
property (a function that uses the SDK code) and a component
property (the UI component for the payment option).
Import paymentServiceModule
into the PaymentOptions
component.
PaymentOptions
is now decoupled from the SDK implementation details, and is ignorant of the particular payment methods.
When I want to extend this feature with a new payment method (i.e., PayPal), I simply slot in a new key/value pair to paymentServiceModule
without having to modify either the PaymentOptions
component or the original payment methods.
The UI code should in theory also be protected against modification if I were to change payment service providers (for 💸 reasons) as long as the payment method’s duck typing remains unchanged.
Am I applying the Open/Closed Principle correctly? Curious to learn other React or JavaScript patterns in the wild that follow this principle.
Bonus
In paymentServiceModule
, lazy load each payment option using the React.lazy
API.
In PaymentOptions
, wrap each payment option in Suspense
, to lazy load the component based on availability.
Resources
- A First Step to Improve Your Code Before Diving into Domain Driven Design or the Clean Architecture by Andréas Hanss
- Revisiting SOLID by Matthew Lucas
Read More
- Intro to React Server Side Rendering
- Decouple Data from UI with React Hooks
- Decouple Data from UI in React Part 2: A further exploration of the Hooks, Render Props, and HOC patterns
Top comments (0)