In this lecture we'll dig into organizing microservice projects. Knowing how to write code is only part of the job. If microservices are poorly organized, the app turns into a tangled, unmanageable mess — like a TV show where plotlines contradict each other and characters act out of nowhere. So let's go over how to structure projects for a microservice architecture the right way.
Imagine you're building a multi-story house where each room is a separate microservice. Everything's fine until someone decides the kitchen should be on the roof and the bathroom in the basement. Sure, nobody forbids that, but it's clearly inconvenient.
Similar problems happen in programming. If your microservices are poorly structured:
- Projects become hard to maintain. Developers get confused about which service is responsible for what.
- Deploy becomes a nightmare. Any change requires recompiling huge chunks of code or reconfiguring the whole app.
- Teams can't work efficiently. Service autonomy is lost when one microservice depends on a ton of others.
So a good architecture isn't just nice to have — it's necessary.
Principles of microservice decomposition
1. Separation of Concerns
Each microservice should be responsible for only one functional area. It's the "do one thing and do it well" ideology. For example:
- User service (
User Service) — handles registration, authentication, and storing profiles. - Order service (
Order Service) — manages the creation and processing of orders. - Payment service (
Payment Service) — handles payments and manages transactions.
Wondering how to decide "where to stop" splitting? Domain-Driven Design (DDD) helps identify the major functional areas of your application.
2. Service independence
Microservices should be as independent as possible. That means:
- Each service owns its own data. Never, I mean NEVER, connect directly to another service's database.
- Minimize inter-service calls. If your service "depends" on many others, a single failure can drag the whole system down.
3. Isolation by business domains
Services should be split based on the business problems they solve. For example:
- If you're building an online store, you can separate services for users, products, orders, and payments.
- If you're building a CRM, split services for managing customers, deals, and marketing campaigns.
Microservice project structure
Now let's look at how to structure the codebase. Here's an example structure:
ecommerce/ # Root project folder
├── user-service/ # User microservice
│ ├── src/ # Source code
│ │ ├── main/
│ │ │ ├── java/com/example/user/ # Packages
│ │ │ │ ├── controller/
│ │ │ │ ├── service/
│ │ │ │ ├── repository/
│ │ │ │ ├── model/
│ │ │ │ └── resources/ # Configuration files
│ │ └── test/
│ └── Dockerfile # Instruction for the container
├── order-service/ # Order microservice
│ ├── src/
│ └── Dockerfile
├── payment-service/ # Payment microservice
│ ├── src/
│ └── Dockerfile
└── config/ # Centralized configurations
├── docker-compose.yml # Launch configuration
└── eureka-config.yml # Service Discovery configuration
A good package structure inside each microservice makes code easy to find:
controller/— put all controllers here (e.g., REST API).service/— business logic lives here.repository/— database access.model/— entity descriptions, e.g.,User,Order.
Practice: splitting an app into microservices
Let's say we're building an online store. We have three major areas:
- Users: registration and authentication.
- Products: catalog management.
- Orders: creating orders and managing them.
We'll create three microservices: UserService, ProductService, OrderService.
- First, create a new project using Spring Initializr (
user-service):- Include dependencies: Spring Web, Spring Data JPA, Spring Boot DevTools, H2 Database.
- Structure the packages:
com.example.user ├── controller/ ├── service/ ├── repository/ ├── model/ - Create a simple entity:
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; // Getters and setters } - Add a controller:
@RestController @RequestMapping("/users") public class UserController { @GetMapping public String getAllUsers() { return "List of users!"; } }
Creating ProductService and OrderService
The process is similar, but use your own domain models (Product, Order) for each service.
How to manage dependencies between services
Sometimes services need to interact with each other. For example, OrderService might query ProductService to check product availability.
Approaches:
- REST API. One service makes an HTTP call to another.
- Asynchronous data exchange. Use message brokers like Kafka if you want to break direct coupling between services.
Helpful tips and common mistakes
- Don't try to make the perfect architecture on the first go. Splitting a system into microservices is often iterative.
- Don't keep too much logic in one microservice. If code starts to balloon and gets complicated, it's a sign to extract a new service.
- Test microservices independently. Make sure each service works fully on its own.
We're just starting to dig into microservice architecture. It's important to remember they're not an end in themselves, but a handy tool to help solve business problems.
GO TO FULL VERSION