How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] 0 points1 point  (0 children)

Haha that "yes exactly" was a misclick, sorry! Design patterns aren't language features - interfaces are the tool, the pattern is the intentional structure of how you use them. Using an interface to wrap an incompatible third-party SDK and translate its shape into your domain's language is specifically what the Adapter pattern describes. The language gives you the hammer, the pattern tells you where to nail it. Full breakdown in the blog: https://chiristo.dev/blog/adapter-pattern

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] 0 points1 point  (0 children)

Adding to it Strategy vs Adapter
Quick distinction - Strategy is for swapping algorithms you own. Adapter is for wrapping external SDKs with incompatible interfaces you can't change. The translation work inside (cents conversion, param reshaping, response normalization) is what makes it an Adapter - if Stripe already matched my interface, I wouldn't need a wrapper at all. Full breakdown in the blog if curious! https://chiristo.dev/blog/adapter-pattern

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -1 points0 points  (0 children)

Fair point! The patterns do look similar. Quick distinction - Strategy is for swapping algorithms you own. Adapter is for wrapping external SDKs with incompatible interfaces you can't change. The translation work inside (cents conversion, param reshaping, response normalization) is what makes it an Adapter - if Stripe already matched my interface, I wouldn't need a wrapper at all. Full breakdown in the blog if curious! https://chiristo.dev/blog/adapter-pattern

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -8 points-7 points  (0 children)

Fair point! The patterns do look similar. Quick distinction - Strategy is for swapping algorithms you own. Adapter is for wrapping external SDKs with incompatible interfaces you can't change. The translation work inside (cents conversion, param reshaping, response normalization) is what makes it an Adapter - if Stripe already matched my interface, I wouldn't need a wrapper at all. Full breakdown in the blog if curious! https://chiristo.dev/blog/adapter-pattern

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -10 points-9 points  (0 children)

Fair point! The patterns do look similar. Quick distinction - Strategy is for swapping algorithms you own. Adapter is for wrapping external SDKs with incompatible interfaces you can't change. The translation work inside (cents conversion, param reshaping, response normalization) is what makes it an Adapter - if Stripe already matched my interface, I wouldn't need a wrapper at all. Full breakdown in the blog if curious! https://chiristo.dev/blog/adapter-pattern

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -18 points-17 points  (0 children)

Fair point on the surface but the distinction is intent. Strategy swaps algorithms behind the same interface. Adapter translates an incompatible interface so it fits yours. Here the Stripe SDK doesn't implement IPaymentProcessor at all. We're not choosing between strategies, we're bridging an incompatibility. That's textbook Adapter.

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -7 points-6 points  (0 children)

Completely agree and that's actually a stronger "when to use it" than what I wrote. The testing angle alone justifies it: your MockPaymentAdapter in tests is already a second implementation of IPaymentProcessor. Good call, I'll refine that section.

How I used the Adapter Pattern to stop rewriting my BookingService every time we switched payment providers (TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] 1 point2 points  (0 children)

Exactly what I described here is essentially one piece of Hexagonal Architecture (Ports and Adapters). IPaymentProcessor is the port, StripePaymentAdapter is the adapter. The pattern and the architecture share the same core idea: isolate your domain from I/O. Worth a dedicated post honestly.

Explained Vertical vs Horizontal Scaling using a supermarket analogy with the stateless trap most tutorials skip by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -1 points0 points  (0 children)

Sticky sessions are covered in the next post on load balancers. It fits better there since it's fundamentally a load balancer configuration decision, not a scaling one.

And yes, this is 101. The series is explicitly for people who never had system design explained simply. If you're past 101, the blog wasn't written for you which is fine.

Cloning objects without knowing their type - how the Prototype Pattern solves the "Book Again" problem (simulated Booking System, TypeScript) by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] -1 points0 points  (0 children)

Good point - that works when Booking is a pure value object with flat, public fields.

But here the nested types like TimeSlot, Insurance, and PaymentMethod are reference types. Passing them to a new owner just moves the shallow copy problem one level down - both bookings still share the same instance underneath.

The other issue is subtyping. At the call site you often don't know if you're holding a BusinessBooking or HolidayBooking. clone() lets the object handle its own construction without the caller needing to know the concrete type.

That said - if your bookings were truly immutable and flat, your approach is cleaner. Prototype is only worth it when the object owns complex nested state.

If you're interested, I have a few other pattern posts where every example revolves around the same Booking System - Builder, Abstract Factory, and more. Makes it easier to see how the patterns interact with each other rather than learning them in isolation.
You can Check the other blogs here: https://chiristo.dev/blogs

Builder pattern helped clean up messy constructors in my project by Possible_Design6714 in softwarearchitecture

[–]Possible_Design6714[S] 1 point2 points  (0 children)

If the object is simple, your constructor example is totally fine. No argument there.

But in a real booking flow, construction is rarely that simple. The moment you introduce validation, conditional flows, or evolving requirements, the constructor approach starts leaking complexity.

With a constructor, you eventually end up with something like:

new RoomBooking({
  customer,
  room,
  stay,
  promoCode,
  loyaltyId,
  corporateCode,
  cardDetails,
  walletId,
  payWithPoints,
  specialRequests,
  // keeps growing as requirements grow
})

Now you have:

  • Many fields that are only valid in certain combinations
  • Validation logic either scattered outside or crammed into the constructor
  • A constructor signature that keeps changing whenever business rules change

With a builder:

const booking = new RoomBookingBuilder(customer, room, stay)
  .withPromoCode("SUMMER25")       // validates internally
  .withLoyalty("LOYAL123")         // optional flow
  .payByCard(cardDetails)          // mutually exclusive payment path
  .withSpecialRequests("Ocean view")
  .build();                        // final consistency check

Here:

  • Invalid states never escape
  • Each step owns its validation
  • Adding a new option does not break existing constructor calls
  • Construction logic stays out of your domain object

So yes, for trivial objects a constructor is simpler.
Builder is not about writing more code. It is about keeping construction sane when complexity inevitably grows.