Architecture & Design Patterns

15 questions found

What is the CQRS (Command Query Responsibility Segregation) pattern, and when is it appropriate to introduce in a Node.js system?

Advanced
CQRS separates the models used for writing data (commands, which change state) from the models used for reading data (queries, which return state), often backed by entirely different data representations or even different databases optimized for each purpose -- this is valuable when read and write workloads have very different scaling or shape requirements (like a system with heavy, complex reporting queries but simple writes), but it's genuinely overkill for most CRUD applications, adding synchronization complexity (especially if paired with eventual consistency between the write and read models) that isn't justified without a real need.
// Command side: normalized write model
async function createOrder(command) {
  await db.orders.insertOne({ id: command.id, items: command.items, status: 'pending' });
  eventBus.publish('OrderCreated', command);
}

// Query side: denormalized read model, updated asynchronously via the event
eventBus.subscribe('OrderCreated', async (event) => {
  await readDb.orderSummaries.insertOne({ id: event.id, itemCount: event.items.length });
});
Real-world example An analytics-heavy e-commerce platform maintains a separate, denormalized read database optimized for dashboard queries, updated asynchronously whenever the normalized write database records a new order, so complex reporting queries never compete for resources with the transactional order-placement path.

Common follow-ups: What's the specific risk of eventual consistency between the write and read models, and how do you communicate that to end users?;What's a simpler intermediate step short of full CQRS for a team not ready for that complexity?

Databases & ORMs (MongoDB/Mongoose SQL/Sequelize);Message Queues (RabbitMQ & Kafka)

What is Dependency Injection, and how is it typically implemented in Node.js given the language has no built-in DI container?

Intermediate
Dependency Injection means a component receives its dependencies (other objects or functions it needs) from the outside, typically via constructor or function parameters, rather than creating or looking them up itself -- this decouples the component from specific implementations and makes substituting mocks or fakes during testing straightforward. Node.js has no built-in DI container the way some other languages do, so DI is commonly implemented manually via constructor parameters and a manual composition step (sometimes called 'poor man's DI'), or via a small library like Awilix, InversifyJS, or tsyringe for larger applications.
// Manual DI via constructor parameters -- no framework needed
class OrderService {
  constructor(orderRepository, notificationService) {
    this.orderRepository = orderRepository;
    this.notificationService = notificationService;
  }
}

// Composition root: where all the wiring happens, typically once at startup
const orderService = new OrderService(new OrderRepository(db), new EmailNotificationService());
Real-world example A team testing their OrderService constructs it with a fake in-memory OrderRepository and a mock NotificationService in their unit tests, while the production composition root wires up the real database-backed repository and real email service -- all without changing a single line of OrderService itself.

Common follow-ups: At what team or project size does adopting a formal DI container like InversifyJS start paying off over manual wiring?;How does DI relate to and support the Dependency Inversion Principle from SOLID?

Testing with Jest Mocha & the Node Test Runner;Design Patterns in JavaScript

What is the Saga pattern, and how does it manage distributed transactions across multiple Node.js microservices?

Advanced
Because distributed systems typically can't use a single ACID database transaction spanning multiple services, the Saga pattern manages a multi-step business process as a sequence of local transactions, each in its own service, with a corresponding compensating action defined for each step to undo it if a later step fails -- implemented either as choreography (each service listens for events and reacts, fully decentralized) or orchestration (a central coordinator explicitly calls each step and triggers compensations on failure).
// Orchestration-style saga (simplified)
async function bookTripSaga(trip) {
  const flight = await bookFlight(trip);
  try {
    const hotel = await bookHotel(trip);
    try {
      return await bookCar(trip);
    } catch (err) { await cancelHotel(hotel); throw err; }
  } catch (err) { await cancelFlight(flight); throw err; }
}
Real-world example A travel-booking platform uses an orchestrated saga to book a flight, hotel, and rental car across three separate microservices; if the car-rental booking fails after the flight and hotel already succeeded, the saga automatically triggers compensating cancelFlight and cancelHotel calls rather than leaving the customer half-booked.

Common follow-ups: What's the practical difference between choreography-based and orchestration-based sagas in terms of operational complexity?;How do you handle the case where a compensating action itself fails?

Message Queues (RabbitMQ & Kafka);Microservices Architecture with Node.js

What is the Strategy pattern, and how would you apply it to support multiple payment providers in a Node.js checkout service?

Intermediate
The Strategy pattern defines a family of interchangeable algorithms behind a common interface, letting the calling code select and swap the concrete implementation at runtime without changing its own logic -- for a checkout service supporting Stripe, PayPal, and bank transfers, each provider implements the same processPayment() interface, and the checkout flow simply calls whichever strategy was selected based on the customer's chosen payment method.
class StripeStrategy { async pay(amount) { return stripeClient.charge(amount); } }
class PayPalStrategy { async pay(amount) { return paypalClient.createPayment(amount); } }

class Checkout {
  constructor(paymentStrategy) { this.strategy = paymentStrategy; }
  async completeOrder(amount) { return this.strategy.pay(amount); }
}

new Checkout(new StripeStrategy()).completeOrder(4999);
Real-world example An online store adds Apple Pay support months after launch by writing a single new ApplePayStrategy class implementing the existing pay() interface, without touching the Checkout class or any of the other existing payment strategies at all.

Common follow-ups: How does the Strategy pattern differ from simply using an if/else chain based on payment type?;How would you make the choice of strategy configurable per merchant rather than hardcoded?

Design Patterns in JavaScript;Payments

What is a Facade pattern, and how might it be used to simplify a Node.js application's interaction with several third-party SDKs?

Advanced
A Facade provides a single, simplified interface over a more complex subsystem of classes or libraries -- rather than every part of an application directly importing and configuring multiple third-party SDKs (payment, email, SMS, analytics) with their own inconsistent APIs, a Facade wraps them behind one consistent internal interface, isolating the rest of the codebase from those libraries' specific quirks and making it easier to later swap out or upgrade an underlying SDK.
class NotificationFacade {
  constructor() { this.email = new SendGridClient(); this.sms = new TwilioClient(); }
  async notifyUser(user, message) {
    if (user.prefersEmail) return this.email.send(user.email, message);
    return this.sms.send(user.phone, message);
  }
}

// Rest of the app only ever calls this simple interface
await notificationFacade.notifyUser(user, 'Your order has shipped');
Real-world example An application originally calling the Twilio SDK directly from a dozen different files is refactored to go through a single NotificationFacade, so that a later migration from Twilio to a different SMS provider only requires changing that one facade class instead of a dozen call sites.

Common follow-ups: What's the difference between a Facade and an Adapter pattern, given both wrap another interface?;At what point does a facade risk becoming an unwieldy 'god object' itself?

Design Patterns in JavaScript;Email & Notifications

Showing 11–15 of 15