Basic
sequenceDiagram Router->>Middleware: Send user Request Router->>Controller: Send user Request Controller->>Controller: Validation & Handle Controller->>Controller: Transform to DTOs Controller->>Service: Perform business-logic Service->>Service: Communicate each others Service->>Service: Call external services Service->>Repository: CRUD actions Repository->>Model: Select/Update data Repository-->>Service: Model data Service-->>Controller: Transform response Controller->>Controller: Transformer process Controller->>View: Response data via view rendering
The request lifecycle represents the complete journey of a user request through your application, from the moment it reaches your server until a response is sent back to the user. Understanding this flow is crucial for developers to follow the execution path, troubleshoot issues, and implement new features effectively.
Function: The router is the entry point for all incoming HTTP requests. Responsibilities:
- Parse the incoming URL and HTTP method
- Match the request against defined routes
- Forward the request to the appropriate middleware and controller
- Handle 404 responses for unmatched routes
Function: Middleware acts as a filter chain that processes requests before they reach the controller. Responsibilities:
- Authentication verification
- CSRF protection
- Request logging
- Session management
- Rate limiting
- Header manipulation
- IP filtering
- Response compression
Middleware can be global (applied to all routes) or route-specific (applied only to certain routes).
Function: Controllers receive the filtered request and orchestrate the application logic. Responsibilities:
- Validation & Handling: Validate input data against defined rules
- Transform to DTOs: Convert raw request data into Data Transfer Objects (DTOs)
- Service Coordination: Delegate business logic to appropriate service(s)
- Response Transformation: Process service results into appropriate response format
- View Selection: Determine which view should render the response
Controllers should remain thin, focusing on request/response handling rather than implementing business logic.
Function: Services contain the core business logic of the application. Responsibilities:
- Perform Business Logic: Execute the actual business rules and processes
- Service Communication: Coordinate with other services when needed
- External Services Integration: Interact with third-party services or APIs
- Transaction Management: Ensure data consistency across operations
Services should be independent of the HTTP layer, making them reusable across different entry points (web, CLI, etc.).
Function: Repositories abstract the data access logic from the business logic. Responsibilities:
- CRUD Operations: Provide methods for Create, Read, Update, and Delete operations
- Query Building: Construct database queries
- Data Filtering: Apply filtering, sorting, and pagination
- Transaction Support: Participate in transaction management
Repositories shield the rest of the application from the specifics of the data storage mechanism.
Function: Models represent the data structure and relationships. Responsibilities:
- Define data structure and validation rules
- Handle relationships between different data entities
- Provide accessor and mutator methods for data properties
- Encapsulate data-specific behavior
After data operations are completed, the response follows a reverse path:
- Repository → Service: Return model data to the service layer
- Service → Controller: Return processed results to the controller
- Controller Transformation: Convert data to the appropriate response format
- View Rendering: Combine data with templates to generate HTML (for web views)
- Response Delivery: Send the completed response back to the client
Depending on the application type, responses may take different forms:
- HTML Responses: Rendered views for traditional web applications
- JSON/XML Responses: Structured data for API consumers
- File Downloads: Binary data for file operations
- Redirects: HTTP redirects to other URLs
- Error Responses: Structured error information
This structured request lifecycle offers several advantages:
- Separation of Concerns: Each component has a single, well-defined responsibility
- Testability: Individual components can be tested in isolation
- Maintainability: Changes to one layer minimally impact other layers
- Scalability: Components can be scaled independently as needed
- Code Reuse: Services and repositories can be used across different controllers or entry points
The request lifecycle can impact application performance:
- Middleware Efficiency: Excessive middleware can add latency
- N+1 Query Problems: Inefficient data access patterns can multiply database queries
- Eager vs. Lazy Loading: Choosing the right data loading strategy affects performance
- Response Size: Large responses increase network transfer time
- Caching Strategies: Strategic caching can bypass parts of the lifecycle for improved performance
Each layer has specific error handling responsibilities:
- Router: Handle 404 (not found) and 405 (method not allowed) errors
- Middleware: Handle authentication and authorization errors (401, 403)
- Controller: Handle validation errors (422) and coordinate error responses
- Service: Handle business logic errors and application exceptions
- Repository: Handle database errors and data access exceptions
When troubleshooting issues, consider these approaches:
- Request Logging: Log the request at each major step
- Performance Profiling: Identify bottlenecks in the request processing
- Exception Tracking: Monitor where and when exceptions occur
- Transaction Monitoring: Track database transactions through the layers
- Request Tracing: Implement distributed tracing for complex applications
For an optimal request lifecycle implementation:
- Keep Controllers Thin: Move business logic to services
- Use DTOs: Create explicit data transfer objects for each layer boundary
- Implement Consistent Error Handling: Use a uniform approach to error processing
- Apply Dependency Injection: Avoid tight coupling between layers
- Document the Flow: Maintain clear documentation on how requests flow through your system
- Consider Asynchronous Processing: Offload long-running tasks from the main request thread
- Validate Early: Catch invalid requests as early as possible in the lifecycle
The request lifecycle provides a structured approach to handling user interactions with your application. By understanding each step in this journey, developers can build more maintainable, testable, and performant applications while ensuring a clear separation of concerns across the different architectural layers.
So it is clear that any Request
will be received from Router
. Immediately after that, Router
will coordinate with Middleware
(if any). If there are no problems, the data will be sent to Controller
for processing.
At Controller
, two main processes will be performed: checking input data. This is a way to ensure that the data that needs to be processed is valid and complete.
The next important step is to process that request. With submitted data, it can be a combination of many things. Therefore, it is necessary to classify data into DTOs
(Data Transfer Object) that need to be processed before putting them into Services
which contain business-logic.
NoteIMPORTANT!
Do not inputRequest
,Form Request
objects ofController
directly intoService
. This can cause problems if you need to callService
inConsole
in feature.
NoteNOTE
Service
is where the logic is performed. What data should be requested from itself. And thatService
can be used in many different places as a caller (Controller
orConsole
). The caller has to createDTO
data thatService
requires.
Service
is the right place to use the persistence layer Repository
and Model
. The Service
can use multiple Repositories
at the same time to create modification requests and query data appropriate for a business request. The Service
can be called each other. In addition, Service
is also the place to generate communication processes with external services.
Repository
will create editing operations or retrieve data from the database through Model
. To facilitate data manipulation, gFly provides Fluent Query Builder, a tool that makes data querying more efficient.
Make sure all the data manipulation steps are complete. Then returning results to the user will require a View
layer. The data received from Service
returned to Controller
is base data. Depending on different display needs, we can change that data before sending it to View
for display. gFly proposes an additional Transformer
handler (optional) to increase data adaptability to View
needs.
To avoid spam registrations. The system only requires users to send basic information such as their email address, first name, last name for registration. And the below processing will be required:
- Save user information: Only responsible for storing data in related tables.
- Activate an account: Send emails to users asking them to activate their accounts and create passwords. In addition, the system also has a reminder mechanism by sending this email reminder every 1 month and repeating it 3 times.
- Notify new user: Send notifications to administrators about newly registered user information.
flowchart TD O["fa:fa-twitter Register User Request"] O-->A[fa:fa-ban RegisterUserDTO] O-->B(fa:fa-ban ActivateAccountDTO) O-->C(fa:fa-camera-retro NotifyNewUserDTO)
Suppose you create a service and receive an HTTP Request
object. Retrieving and processing data is inside the service. If one day there is a requirement to write a function to import users into the system using a CLI console to read data from CSV or Excel files, you will definitely encounter difficulties and have to rewrite those services. If you create the DTOs needed to request a service to run, then you can now definitely use it without having to write anything else. In addition, if you combine the 3 services above, you may encounter some difficulties requesting "Re-send email to alert user to activate account"
in the future.
NoteTIP
Don’t be afraid to create `DTOs’ and use them for components and layers in your code.