Backend Frameworks (Node.js/Django)

Chapter 11
Architecture and Distributed Backend Design

Bachelors Program in Full Stack Development
Backend Frameworks (Node.js/Django)   •   Bachelor

Architecture and Distributed Backend Design

Introduction and Context

Backend architecture is the organization of software responsibilities, dependencies, data, communication paths, and operational controls. In a Node.js or Django system, architecture determines more than the location of files: it influences testability, deployment, security, scalability, failure handling, and the cost of future change.

A small application can often be implemented as one process and one database. As requirements grow, however, the system may need separate modules, asynchronous processing, external integrations, independent deployment, or geographically distributed services. These changes introduce trade-offs rather than automatic improvements. A microservice architecture, for example, may provide independent scaling but also introduces network failures, distributed observability, data-consistency problems, and operational overhead.

The central architectural question is therefore not “Which pattern is fashionable?” but “Which arrangement best satisfies the system’s functional and quality requirements under realistic constraints?”

Learning Objectives

By the end of this chapter, students should be able to:

  1. Explain layered architecture, MVC/MTV, service, repository, and domain-oriented designs.
  2. State dependency directions correctly and distinguish abstractions from implementations.
  3. Compare modular monoliths and microservices using technical and organizational criteria.
  4. Design API gateways and synchronous or asynchronous service communication.
  5. Explain eventual consistency, sagas, idempotency, retries, timeouts, and circuit breakers.
  6. Evaluate architectural alternatives using scalability, reliability, security, cost, and maintainability.
  7. Apply these concepts to a realistic backend system.

1. Architectural Foundations

1.1 Components, responsibilities, and boundaries

A component is a unit with a recognizable responsibility and interface. A boundary defines what a component owns and what it may know about other components. Good boundaries reduce accidental coupling and make change local.

A useful architectural description identifies:

  • Responsibilities: what a component does.
  • Interfaces: operations and data it exposes.
  • Dependencies: services, libraries, databases, or infrastructure it requires.
  • Data ownership: which component is authoritative for particular records.
  • Quality attributes: latency, availability, security, recoverability, and observability requirements.

Architecture should be evaluated at several levels: code structure, process boundaries, deployment topology, and organizational ownership. A well-modularized codebase can be deployed as one process, whereas several services may be deployed independently. These are related but different decisions.

1.2 Coupling and cohesion

Cohesion measures how strongly the responsibilities within a module belong together. Coupling measures how much one module depends on others. High cohesion and deliberately controlled coupling are generally desirable.

Coupling is not simply bad. A payment component must depend on a payment provider, and an order process must depend on order data. The design goal is to make dependencies explicit, stable, and replaceable where replacement is valuable. Excessive indirection can itself reduce clarity.

2. Layered Architecture and Dependency Direction

A conventional layered backend may contain:

  1. Presentation or interface layer: HTTP routes, controllers, serializers, authentication adapters, and input validation.
  2. Application layer: use cases, transaction coordination, authorization decisions, and workflow orchestration.
  3. Domain layer: business concepts, invariants, entities, value objects, and domain services.
  4. Infrastructure layer: databases, message brokers, file systems, email providers, and external APIs.

The critical principle is that dependency direction should point toward stable business abstractions, not toward technical details. In a repository-oriented or domain-driven design, the domain should not import a Django ORM model, a Node.js database driver, or a cloud SDK merely to express a business rule.

A more accurate arrangement is:

  • Interface depends on application contracts.
  • Application depends on domain concepts and on abstractions needed to perform use cases.
  • Domain depends on neither infrastructure nor a particular user interface.
  • Infrastructure implements interfaces or ports defined by the application or domain boundary.

Thus, the source-code dependency may be represented as:

Interface → Application → Domain

while Infrastructure → Application/Domain abstractions. At runtime, the application uses an injected infrastructure implementation. This is dependency inversion: the high-level policy defines the contract, and the low-level mechanism conforms to it. The domain is therefore independent of infrastructure, even though the deployed system connects the two.

A common error is to state simply “Interface → Application → Domain → Infrastructure.” That diagram suggests that domain code depends on infrastructure, which contradicts the independence required by repository and domain-driven designs. A framework may impose a different practical structure, but such coupling should be recognized as a trade-off rather than described as the ideal dependency direction.

2.1 Ports and adapters

A port is an application- or domain-facing interface, such as UserRepository or PaymentGateway. An adapter implements that port for a particular technology, such as PostgreSQL, MongoDB, Stripe, or an HTTP client.

In Django, a model-backed repository may translate between ORM records and domain objects. In Node.js, a TypeScript interface can define a repository contract while a PostgreSQL adapter implements it. Dependency injection, constructor parameters, or a composition root connect the implementation to the use case.

This structure improves unit testing: a use case can receive an in-memory fake repository without connecting to a real database. It does not mean every class needs an interface. Abstractions should be introduced where they protect a meaningful boundary or support a realistic variation.

3. MVC and MTV in Backend Frameworks

Model–View–Controller (MVC) separates data and business state, presentation, and request coordination:

  • The model represents data and often domain behavior.
  • The view produces a representation.
  • The controller interprets a request and coordinates a response.

Django describes its organization as Model–Template–View (MTV). Django’s model handles persistence representation, the template renders presentation, and the view function or class processes the request. Django’s URL configuration also performs routing. In practical terms, Django’s “view” often plays a role similar to an MVC controller, while Django’s template plays the presentation role.

Node.js frameworks vary. Express commonly leaves MVC boundaries to the developer; NestJS provides controllers, providers, modules, and dependency injection. Regardless of terminology, a route handler should not become the entire application. Controllers should validate or delegate, while use-case services should coordinate business actions.

MVC/MTV is primarily a presentation and request-organization pattern. It does not automatically provide domain modeling, distributed architecture, transactional design, or service boundaries. These concerns must be designed separately.

4. Service and Repository Patterns

A service layer exposes application operations such as placeOrder, registerStudent, or issueRefund. It coordinates validation, authorization, domain behavior, persistence, and external communication. A service should express a meaningful use case rather than become a miscellaneous “utils” class.

A repository provides collection-like operations for domain-relevant persistence, such as findActiveSubscription or saveOrder. It hides storage mechanics from the calling policy. Repositories should not become generic wrappers around every database method; their interfaces should be shaped around the needs of the use cases.

A typical request flow is:

HTTP request → controller/view → application service → domain objects → repository port → infrastructure adapter → database

The reverse path returns a result or a domain error, which the interface layer maps to an HTTP response.

Transaction boundaries

A service should identify the transaction boundary explicitly. A transaction may safely cover several updates in one database when the database provides the required atomicity. It usually cannot atomically cover a local database and an external payment provider. That second situation requires a protocol such as an outbox, a saga, compensation, or a provider-specific reconciliation process.

5. Domain-Driven Concepts

Domain-driven design (DDD) emphasizes the structure of the business domain and a shared language between technical and domain experts.

  • A bounded context is a boundary within which terms and models have consistent meanings.
  • An entity has an identity that persists through changes.
  • A value object is defined by its attributes and is usually immutable, such as Money or EmailAddress.
  • An aggregate is a consistency boundary containing an aggregate root through which modifications are performed.
  • A domain service expresses a domain operation that does not naturally belong to one entity.
  • An anti-corruption layer translates between a local model and an external or legacy model.
  • A published language is a deliberately defined integration representation.

DDD does not require microservices. A bounded context can be a module in a modular monolith. Nor does every application need elaborate aggregates. The appropriate model depends on business complexity and the cost of enforcing invariants.

Aggregate and consistency example

Suppose an Order aggregate contains order lines and a total. The aggregate root may enforce that an order cannot be paid twice and that a line quantity is positive. A separate Inventory context should not directly edit the order’s internal rows. It may receive an OrderPlaced event and reserve stock according to its own rules.

This separation avoids a shared internal model, but it means the system must handle delayed updates and failure between contexts.

6. Modular Monoliths

A modular monolith is one deployable application whose internal modules have explicit boundaries. Each module may own its application logic, domain model, and persistence access. Modules communicate through public interfaces or in-process events rather than by importing one another’s internal classes or querying one another’s tables directly.

Advantages include:

  • one deployment pipeline and simpler local development;
  • low-latency in-process calls;
  • straightforward database transactions where appropriate;
  • easier debugging than a distributed system;
  • the possibility of later extracting a genuinely independent module.

Risks include a shared database becoming an informal integration API, uncontrolled imports, and a “big ball of mud” hidden inside one process. A modular monolith is successful only when boundaries are enforced through code review, package rules, schema ownership, and tests.

For many university and business systems, a modular monolith is the prudent starting point. It preserves simplicity while allowing domain boundaries to mature.

7. Microservices and Distributed Boundaries

A microservice is an independently deployable component that usually owns a focused business capability and its data. The term does not specify a particular size. A service boundary is useful when a capability has distinct scaling needs, release cadence, security requirements, technology constraints, or organizational ownership.

Potential benefits are independent deployment, fault isolation, selective scaling, and team autonomy. Costs include network latency, operational tooling, distributed tracing, versioned contracts, duplicated infrastructure, harder testing, and distributed data management.

A service should normally own its data. Direct cross-service database queries create hidden coupling and make independent evolution difficult. Shared reference data may be replicated or exposed through an API, with explicit freshness expectations.

A decision framework

Before extracting a service, ask:

  1. Is there a clear business boundary?
  2. Can the capability be deployed and operated independently?
  3. Is its scaling or security profile materially different?
  4. Can the organization support monitoring, incident response, and on-call work?
  5. What consistency guarantees are required?
  6. What is the migration and rollback plan?

If the answers are weak, a modular monolith may provide greater total value.

8. API Gateways and Edge Responsibilities

An API gateway is an entry point that can route requests, terminate TLS, authenticate tokens, enforce quotas, transform protocols, and provide a client-specific aggregation layer. It may also support request correlation, threat filtering, and coarse-grained authorization.

The gateway should not become a second business application. Complex domain rules belong in the owning service. Excessive orchestration in the gateway creates a central bottleneck and makes internal services difficult to use through other channels.

A gateway can expose a stable external API while internal services evolve. However, transformations must be documented, observable, and versioned. Authentication at the gateway does not eliminate authorization checks inside services; a compromised or misconfigured internal caller must not gain unrestricted access.

9. Service Communication

Synchronous communication

HTTP/REST and gRPC are common synchronous mechanisms. The caller waits for a response, making the interaction easy to understand but coupling availability and latency. Timeouts are essential: without them, a stalled dependency can consume worker resources indefinitely.

A timeout should reflect a stated user or workflow budget. Retrying every failure is unsafe. Retries are generally appropriate only for transient failures, with bounded attempts, exponential backoff, and jitter. A non-idempotent operation such as “charge card” must not be blindly retried unless an idempotency key or equivalent provider guarantee prevents duplication.

Asynchronous communication

Message brokers and event streams decouple producers from consumers. A producer publishes an event such as OrderPlaced; consumers process it independently. This can improve resilience and throughput but introduces delivery delay, duplicate messages, reordering, poison messages, and more complex debugging.

Consumers should be idempotent. A common implementation stores a processed-event identifier with the resulting state, or uses a database constraint that makes repeated application harmless.

The transactional outbox

A service that updates its database and publishes an event faces a dual-write problem: the database update may succeed while event publication fails. The transactional outbox pattern writes the business change and an outbox record in one local database transaction. A relay later publishes the outbox record and marks it delivered. Consumers still need idempotency because publication or acknowledgement can be repeated.

10. Eventual Consistency and Sagas

In a distributed system, replicas or services may temporarily disagree after a valid update. Eventual consistency means that, if updates stop and communication continues, replicas will converge. It does not mean “inaccurate”; it means the system explicitly permits a period in which a read may be stale.

Designers must specify:

  • which data may be stale and for how long;
  • which operations require immediate consistency;
  • how users are informed of pending state;
  • how conflicts are detected and resolved;
  • how reconciliation is performed.

A saga coordinates a business process made of local transactions. Each step has a compensating action where possible. For example, an order saga may reserve inventory, authorize payment, and arrange shipment. If shipment cannot be arranged, the saga may release inventory and void payment authorization. Compensation is not the same as rollback: an email cannot truly be “un-sent,” and a financial transaction may require a reversal rather than erasure.

A choreography-based saga uses events and decentralized reactions. An orchestration-based saga uses a coordinator that tracks state and commands participants. Choreography can reduce central control but may become difficult to understand; orchestration improves visibility but creates a coordinator that must be designed for failure.

11. Resilience Engineering

Resilience is the ability to continue useful operation, degrade safely, and recover after failure.

Important techniques include:

  • Timeouts: bound waiting time.
  • Retries: recover from selected transient faults, with limits and backoff.
  • Circuit breakers: temporarily stop calls to a failing dependency, allowing recovery and protecting callers.
  • Bulkheads: isolate resource pools so one failure does not exhaust all workers.
  • Rate limits: protect services from overload.
  • Load shedding: reject lower-priority work when capacity is exhausted.
  • Fallbacks: return cached or reduced functionality only when the result remains safe.
  • Health checks: distinguish process liveness from readiness to serve traffic.
  • Dead-letter queues: isolate messages that repeatedly fail.

A fallback must not silently violate a business invariant. Showing an old product description may be acceptable; approving a payment using stale authorization data may not be.

Availability composition

If a request requires two independent services sequentially, and both must succeed, the idealized availability is approximately the product of their availabilities:

Apath = A1 × A2

Here, A1 and A2 are probabilities that each service is available during the measurement period. This model assumes independence and ignores retries, caching, correlated failures, and gateway failure; real systems may differ. If A1 = 0.99 and A2 = 0.99, then Apath = 0.99 × 0.99 = 0.9801, or 98.01%. Adding synchronous dependencies can therefore reduce idealized path availability.

For redundant independent alternatives where either service can satisfy the request, availability is:

Aredundant = 1 − (1 − A1)(1 − A2)

This formula applies only when either path is sufficient and failures are independent. With two 99% alternatives, it gives 1 − (0.01 × 0.01) = 0.9999, or 99.99%. Correlated failures, such as a shared region or database, invalidate the simple calculation.

12. Observability, Security, and Governance

Distributed systems require correlated logs, metrics, and traces. A request or message should carry a correlation or trace identifier. Useful metrics include latency percentiles, error rate, throughput, queue age, retry volume, circuit-breaker state, and saturation. Logs should be structured and should avoid passwords, access tokens, payment credentials, and unnecessary personal data.

Security responsibilities include authentication, authorization, input validation, secret management, encryption in transit, least privilege, dependency patching, auditability, and threat modeling. Service-to-service authentication should not rely solely on network location. Gateway controls and internal policy enforcement should complement one another.

Privacy and regulatory obligations may require data minimization, retention limits, access records, deletion workflows, residency controls, and breach response. Event logs and backups complicate deletion; retention architecture must be considered before data is replicated widely.

AI-assisted coding can accelerate scaffolding and test generation, but generated code must be reviewed for insecure defaults, license obligations, hallucinated APIs, data leakage, and inadequate error handling. AI does not replace architectural accountability or threat analysis.

13. Worked Case: Illustrative University Course-Commerce Platform

Background

Consider an illustrative platform through which students enroll in courses, pay fees, receive confirmations, and access digital materials. The initial application is a Django monolith with PostgreSQL.

Problem

The system experiences slow enrollment during registration periods. Payment-provider failures sometimes leave enrollment status unclear. The team proposes immediately creating six microservices.

Analysis

The business capabilities are Enrollment, Payments, Catalog, Identity, and Notifications. Enrollment requires strong local consistency for seat allocation. Notifications can tolerate delay. Payments are external and cannot participate in the local database transaction. The team has four developers and limited operations experience.

Approach

The recommended first step is a modular monolith:

  • Enrollment owns seat allocation and enrollment state.
  • Catalog owns course descriptions and capacity configuration.
  • Payments owns payment attempts and provider integration.
  • Notifications consumes internal events and sends email asynchronously.
  • Identity remains a shared cross-cutting capability with explicit authorization policies.

Each module exposes application interfaces and owns its persistence access. The enrollment transaction writes the enrollment and an outbox event. A worker publishes EnrollmentRequested or EnrollmentConfirmed. The payment module uses an idempotency key for provider calls and records provider references. A reconciliation job compares local payment status with provider reports.

Only after observing a stable boundary and a genuine scaling or ownership need should Payments or Notifications be extracted into separate deployable services. If extraction occurs, the new service owns its database; the enrollment module communicates through a versioned API or events rather than querying payment tables.

Outcome

The modular design reduces immediate operational complexity, preserves local transactions for seat allocation, and makes notifications non-blocking. The outbox and reconciliation process make payment uncertainty visible rather than pretending that a distributed transaction exists. Later extraction remains possible, but it is based on evidence.

Learning points

  1. A modular monolith can provide strong boundaries without network overhead.
  2. External payment operations require explicit state machines and reconciliation.
  3. Eventual consistency is acceptable when the user interface communicates pending states.
  4. Service extraction should follow stable business boundaries and operational readiness.

14. Architectural Trade-offs and Best Practices

No architecture maximizes every quality attribute. Centralized transactions simplify consistency but limit independent deployment. Independent services improve autonomy but create network and operational complexity. Strong normalization reduces duplication but may increase read coordination. Replication improves read scalability but introduces freshness and conflict concerns.

A disciplined design process should:

  1. State functional requirements and quality attributes.
  2. Identify domain boundaries and ownership of data.
  3. Select the simplest architecture satisfying current constraints.
  4. Define failure modes, timeouts, retries, and recovery procedures.
  5. Specify contracts, versioning, and compatibility rules.
  6. Instrument the system before scaling it.
  7. Test module boundaries, contract compatibility, and failure scenarios.
  8. Review architecture using measurable evidence rather than fashion.

Useful tests include unit tests for domain rules, integration tests for adapters, contract tests for service interfaces, end-to-end tests for critical journeys, and resilience tests for dependency failure. Database migrations, event schemas, and API versions should be treated as production interfaces.

Chapter Summary

Layered architecture organizes responsibilities, but correct dependency direction is essential. Interface and application code may depend inward on domain concepts; infrastructure adapters implement abstractions defined by the application or domain boundary. The domain should remain independent of infrastructure in a repository or DDD design.

MVC and Django’s MTV clarify request and presentation responsibilities but do not solve domain modeling or distributed coordination. Service and repository patterns can separate use-case policy from persistence mechanisms when their abstractions remain meaningful. DDD contributes bounded contexts, aggregates, entities, value objects, and translation layers, without requiring microservices.

A modular monolith often offers an effective balance of simplicity and boundary discipline. Microservices are justified by clear independent capabilities, scaling or security needs, and operational readiness. Gateways should handle edge concerns without becoming repositories of business logic. Synchronous calls require timeouts and carefully controlled retries; asynchronous communication requires idempotency, observability, and message-failure handling.

Eventual consistency, outbox processing, sagas, compensation, circuit breakers, bulkheads, and reconciliation are responses to distributed failure—not optional decoration. Architecture should be selected through explicit trade-offs involving consistency, availability, latency, cost, security, team capability, and long-term change.

Key Terms

Architecture: The organization of software responsibilities, dependencies, interfaces, and deployment structures.

Layered architecture: A design that separates presentation, application, domain, and infrastructure responsibilities.

Dependency inversion: A principle in which high-level policy defines abstractions and low-level mechanisms depend on them.

Port: An interface through which application or domain policy communicates with an external capability.

Adapter: An implementation that translates a port into a technology-specific mechanism.

MVC: Model–View–Controller organization of state, presentation, and request coordination.

MTV: Django’s Model–Template–View organization.

Application service: A component that coordinates a business use case.

Repository: An abstraction for domain-relevant persistence operations.

Bounded context: A boundary within which a domain model and terminology have consistent meaning.

Aggregate: A consistency boundary with an aggregate root controlling modifications.

Modular monolith: One deployable application with explicitly separated internal modules.

Microservice: An independently deployable component focused on a business capability.

API gateway: An edge component that routes and governs client requests.

Idempotency: The property that repeating an operation produces the same intended effect as performing it once.

Eventual consistency: A model allowing temporary disagreement before replicas or services converge.

Saga: A distributed business process composed of local transactions and compensating actions.

Transactional outbox: A pattern that stores a business change and an event record in one local transaction.

Circuit breaker: A control that temporarily stops calls to a failing dependency.

Observability: The ability to understand system behavior through logs, metrics, and traces.

Review Questions

Short Answer Questions

  1. Why should domain code normally remain independent of infrastructure?
  2. What is the practical relationship between Django MTV and conventional MVC?
  3. What problem does a repository abstraction solve?
  4. Give two benefits and two costs of microservices.
  5. Why are idempotency and bounded retries important in distributed systems?

Long Answer Questions

  1. Explain layered architecture, ports and adapters, and dependency inversion using a Node.js or Django example.
  2. Compare a modular monolith with microservices for a growing online learning platform.
  3. Explain bounded contexts, aggregates, entities, and value objects, and discuss when each is useful.
  4. Describe the transactional outbox pattern and explain why it does not eliminate the need for idempotent consumers.
  5. Discuss how API gateways, service communication, observability, and resilience controls interact in a distributed backend.

Analytical / Application Questions

  1. A checkout workflow calls inventory, payment, and shipping synchronously. Analyze the reliability and latency risks and propose a revised design.
  2. Design a saga for course enrollment with payment authorization, seat allocation, and notification. Identify local transactions, events, compensation, and user-visible pending states.
  3. A four-person team wants to split a Django monolith into eight microservices before measuring its bottlenecks. Evaluate the proposal and recommend an evidence-based architectural path.

Multiple Choice Questions with Answers

Question 1

Which dependency direction best represents a repository-oriented design with an infrastructure-independent domain?

A. Domain → Infrastructure → Interface

B. Interface → Application → Domain, with Infrastructure implementing inward-facing abstractions

C. Infrastructure → Domain → Interface, with no application layer

D. Database → Domain → Controller → Infrastructure

Correct Answer: B. Interface → Application → Domain, with Infrastructure implementing inward-facing abstractions

Question 2

In Django’s MTV terminology, which component most commonly performs the request-coordination role associated with an MVC controller?

A. The model

B. The template

C. The view

D. The migration

Correct Answer: C. The view

Question 3

What is the primary purpose of a repository abstraction?

A. To expose database tables directly to clients

B. To hide persistence mechanisms behind domain-relevant operations

C. To replace all domain objects with ORM records

D. To provide network routing between microservices

Correct Answer: B. To hide persistence mechanisms behind domain-relevant operations

Question 4

Which statement about a modular monolith is correct?

A. It must contain one database table shared freely by all modules

B. It is a distributed system with one process per module

C. It is one deployable application with explicit internal module boundaries

D. It cannot later be decomposed into services

Correct Answer: C. It is one deployable application with explicit internal module boundaries

Question 5

A service has availability 0.99 and a second required sequential service also has availability 0.99. Under the stated independence assumptions, what is the idealized path availability?

A. 0.9801

B. 0.9900

C. 0.9999

D. 1.9800

Correct Answer: A. 0.9801

Question 6

What is the main purpose of a transactional outbox?

A. To make every external API call part of one distributed database transaction

B. To atomically record a local business change and an event for later publication

C. To eliminate duplicate message delivery permanently

D. To replace all message brokers with database queries

Correct Answer: B. To atomically record a local business change and an event for later publication

Question 7

Which operation most clearly requires an idempotency mechanism before safe automatic retries?

A. Retrieving a public course description

B. Reading a cached health-check result

C. Charging a customer through a payment provider

D. Loading a static JavaScript file

Correct Answer: C. Charging a customer through a payment provider

Question 8

What does eventual consistency explicitly permit?

A. Permanent disagreement between all replicas

B. Temporary stale or divergent reads before convergence

C. Updates without any conflict-resolution policy

D. Removal of the need for data ownership

Correct Answer: B. Temporary stale or divergent reads before convergence

Question 9

Which responsibility should generally remain inside the owning business service rather than be placed in an API gateway?

A. TLS termination

B. Request routing

C. Complex payment-domain rules

D. Coarse-grained rate limiting

Correct Answer: C. Complex payment-domain rules

Question 10

What is the principal function of a circuit breaker?

A. To encrypt database columns

B. To stop or limit calls to a failing dependency and protect the caller

C. To guarantee exactly-once message delivery

D. To replace authentication with network location

Correct Answer: B. To stop or limit calls to a failing dependency and protect the caller

Backend Frameworks (Node.js/Django)
Chapter 11 of 14