What are controllers, services, repositories, middlewares and request context?
This video provides a detailed explanation of three interconnected backend development concepts: handlers/services/repositories, middleware, and request context. These topics are presented cohesively to clarify their distinct responsibilities and integration within the lifecycle of an HTTP request in a scalable, maintainable backend system.
1. Handlers, Services, and Repositories: Layered Backend Architecture
The video introduces the division of responsibilities in the backend architecture into three major components to promote scalability, maintainability, debugging ease, and feature extensibility.
Key Responsibilities and Workflow
- Handler/Controller: First touchpoint after routing; responsible for extracting and deserializing data from the HTTP request object, validating and transforming the inputs, and then passing these inputs downstream.
- Service Layer: Contains business logic and orchestrates workflows. It is independent of HTTP-specific concerns and handles computations, external API calls, email sending, notification dispatch, and coordinates repository calls.
- Repository Layer: Responsible exclusively for database operations such as querying, inserting, updating, or deleting data. Each repository method should have a single responsibility, i.e., fetching all books or a single book must be handled by separate methods without overloading with optional parameters.
Detailed Process Flow in Handlers
- Receive two default objects typically: request and response.
- Deserialize JSON payload into native language constructs (e.g., structs in Go, dictionaries/classes in Python).
- Perform validation to ensure mandatory fields, data format correctness, and security aspects.
- Optionally run transformations—modifications for convenience downstream, such as setting default values for missing optional query parameters.
- Call Service Layer, passing all validated and transformed data alongside authentication details if applicable.
- Handle the HTTP response based on service results, including setting appropriate HTTP status codes (200, 201, 204 for success; 400 for bad requests; 500 for server errors).
Service Layer Characteristics
- Abstracted from HTTP concerns; no direct interaction with HTTP requests or responses.
- Coordinates with multiple repository methods if needed, combining and transforming data.
- Can handle other processing like sending emails or external API calls.
Repository Layer Characteristics
- Constructs and executes database queries.
- Returns only data relevant to its designated responsibility.
- Does not combine multiple data types or return multiple unrelated datasets.
2. Middleware: Common Logic in the Request Lifecycle
Middleware functions execute "in the middle" of the request lifecycle, intercepting requests or responses without duplicating logic across handlers.
Middleware Execution Characteristics
- Receive request, response, and a next function to pass control to the next middleware or handler.
- Can modify request/response objects or terminate the request early by sending a response.
- Middleware execution order is crucial because requests flow sequentially through them until a response is sent.
Why Middleware is Used
- To avoid code duplication by handling common, cross-cutting concerns once rather than in every handler.
- To reduce server load by potentially rejecting requests early (e.g., failed authentication).
- To maintain a clean separation between common request processing (security, logging) and specific business logic (handlers/services).
Common Examples of Middleware
Ordering Example
Middleware is arranged so that:
- CORS runs first (reject illegal origins early).
- Logging captures all requests.
- Authentication validates tokens to authorize user requests.
- Business logic handlers execute only on authorized requests.
- Global error handler catches unhandled errors at the end.
- Compression applies before the response is sent.
3. Request Context: Shared State in the Request Lifecycle
Request context is a scoped, shared storage tied to the lifetime of a single HTTP request, accessible throughout middleware and handler layers.
Purpose of Request Context
- Enables passing request-scoped metadata (like user ID, role, permissions) transparently across middlewares and handlers.
- Avoids tight coupling by not explicitly passing all parameters between functions.
- Stores various data such as:
- Authentication results (e.g., UserID, Roles).
- Request identifiers (UUIDs) for tracing and logging.
- Cancellation deadlines to abort downstream calls (helpful in microservices).
Typical Usage Flow
- Authentication middleware authenticates and stores the user ID and roles inside the context.
- Downstream handlers read the user identity from the context (never trusting client-supplied user IDs).
- Middleware for request tracing generates a unique request ID stored in the context, used in logs and internal API calls.
- Handlers and services can access this shared state for authorization checks and auditing.
Benefits
- Decouples components by maintaining isolated, request-scoped state.
- Ensures security: avoids using client-supplied user identifiers directly.
- Simplifies passing metadata across asynchronous or layered processing within the request lifecycle.
Summary Table: Component Scope and Responsibilities
Key Insights
- Separating concerns into Handler, Service, Repository layers produces clean, maintainable backend code. HTTP-specific tasks reside in handlers; business logic in services; database interaction in repositories.
- Middleware acts as reusable building blocks for common request lifecycle logic, invoked sequentially before main handlers, helping with security, logging, and request pre-processing.
- Request context is vital for passing request-scoped metadata transparently, especially for authenticated user info, request tracing IDs, and cancellation control.
- Middleware ordering matters significantly to produce correct and efficient request processing, ensuring security checks and error handling happen at appropriate stages.
- Deserialization and validation in handlers safeguard the system against malformed or malicious inputs, enforcing API contracts and improving robustness.
- Repository methods should be small, single-responsibility units for clarity and maintainability.
This comprehensive architecture pattern is applicable across popular backend technologies and languages, supporting scalable, secure, and maintainable API server design.