Lido: A Lightweight Alternative to DDD
When it comes to structuring an application, Domain-Driven Design (DDD) is often hailed as the gold standard. However, DDD can sometimes be too rigid and complex for simpler applications. This blog post presents a straightforward and lightweight alternative that can help you maintain a clean and organized codebase without the overhead of full-blown DDD: LiDo - LightDomain.
Package Structure
A well-structured package hierarchy is crucial for maintaining clarity and ease of navigation in your codebase. Here’s a simple approach:
Domain-Based Packages: Create separate packages for each domain. This ensures that all related components (controllers, services, repositories, and POJOs/DTOs) are grouped together, making it easier to understand and manage.
Avoid Mixing Domains: Never mix different domains within the same package. This can lead to confusion and make the codebase harder to navigate.
Example Package Structure
myapp
│
├── user
│ ├── UserController
│ ├── UserService
│ ├── UserRepository
│ ├── UserDb
│ └── UserDto
│
├── product
│ ├── ProductController
│ ├── ProductService
│ ├── ProductRepository
│ ├── ProductDb
│ └── ProductDto
│
└── order
├── OrderController
├── OrderService
├── OrderRepository
├── OrderDb
└── OrderDto
File Responsibilities
Controllers
Controllers are responsible for handling incoming HTTP requests and mapping them to the appropriate services. They also handle security checks, request validation, and error handling.
- Mapping HTTP Requests: Map incoming HTTP requests to service calls.
- Security Checks: Ensure the user has access to the endpoint.
- Request Validation: Validate incoming requests to ensure they meet the required criteria.
- Service Calls: Delegate the actual work to the services.
- Error Handling: Convert service errors into clean HTTP error responses.
Services
Services act as the intermediary between the HTTP layer and the database. They perform the actual business logic and often convert between DTOs like OrderDto (used in the HTTP layer) and database entities like OrderDb.
- Business Logic: Perform the actual work with the data, often combining multiple repositories.
- DTO to Entity Conversion: Convert between HTTP DTOs and database entities.
Repositories
Repositories handle plain database access. Using plain SQL with minimal magic is often better in the long term than relying on complex ORM frameworks like JPA.
- Database Access: Perform CRUD operations on the database.
- Plain SQL: Use plain SQL queries to interact with the database, minimizing magic and boilerplate code.
Database Entities
Database entities represent rows in the database and are created by the repository.
- Representation: Represent a database row.
DTO Entities
DTO entities represent the data transferred over HTTP and are created by the controller.
- Representation: Represent data received or sent in the HTTP layer.
On Testing
Testing is crucial for maintaining a reliable application. Here are some best practices:
- End-to-End Testing: Use Testcontainers for end-to-end testing. Reuse containers for faster tests.
- Controller Testing: While full end to end testing is amazing is is sometimes also nice to simply verify that entity conversion from http to Dtos works and that e.g. security is validated properly. To that end the Controllers can be tested and the rest (Services and below) can be mocked.
- Service Testing: Test services with a real repository using a Testcontainer.
- Layer-by-Layer Testing: If needed, test each layer (controller, service, repository) separately.
Conclusion
This lightweight approach to structuring your application offers a balance between simplicity and organization. By using domain-based packages and clearly defining the responsibilities of each file type, you can maintain a clean and understandable codebase.
LiDo is a perfect choice for any modern web framework such as Express, Spring Boot and many more.
Feel free to adapt this structure to fit your specific needs and project requirements. Happy coding!