Java Microservices Design with Spring Boot & DDD | SC Guide

හැඳින්වීම: නූතන Software ලෝකයේ Microservices වැදගත්කම
ලංකාවේ Software Engineering ලෝකය දියුණු වෙනකොට, අපි හැමෝටම දැනෙන දෙයක් තමයි, traditional monolithic applications වලට වඩා, කුඩා, ස්වාධීන කොටස් වලින් හදන applications වලට demand එක වැඩි වෙනවා කියන එක. මේකට අපි කියන්නේ Microservices Architecture කියලා. Monolithic applications වල තියෙන complexity, maintainability issues, scalability challenges වගේ දේවල් වලට හොඳම විසඳුමක් විදිහට තමයි Microservices ඉදිරියට එන්නේ.
හිතන්න, ඔයාගේ app එක ලොකු ගොඩනැගිල්ලක් වගේ නම්, monolithic app එකක් කියන්නේ හැමදේම එකට හදපු, එක කැබැල්ලක්. ඒත් Microservices architecture එකක් කියන්නේ, ඒ ගොඩනැගිල්ලේ තියෙන හැම කාමරයක්ම (service එකක්) වෙන වෙනම හදලා, ඒ කාමර වලට තනියම වැඩ කරන්න පුළුවන් විදිහට හදන එක. මේකේ වාසිය තමයි, එක කාමරයක් කැඩුනොත්, මුළු ගොඩනැගිල්ලම කඩා වැටෙන්නේ නැති එක.
මේ Guide එකෙන් අපි බලමු, Java භාවිතා කරලා, විශේෂයෙන්ම Spring Boot Framework එකයි, Domain-Driven Design (DDD) මූලධර්මයි යොදාගෙන Microservices හොඳට Design කරන්නේ කොහොමද කියලා. ඔයා දැන්ම Microservices වලට අලුත් කෙනෙක් වුණත්, දැනටමත් වැඩ කරන කෙනෙක් වුණත්, මේක ඔයාට ගොඩක් වැදගත් වෙයි. එහෙනම් අපි පටන් ගමු!
1. Microservices කියන්නේ මොනවද? Monolith එකෙන් වෙනස් වෙන්නේ කොහොමද?
සරලවම කියනවා නම්, Microservices කියන්නේ application එකක්, කුඩා, ස්වාධීන, ලිහිල්ව සම්බන්ධ (loosely coupled) වූ services ගොඩක් විදිහට ගොඩනැගීමයි. මේ සෑම service එකකටම තමන්ගේම codebase එකක්, database එකක් (සමහරවිට), සහ තමන්ගේම deployment cycle එකක් තියෙන්න පුළුවන්.
Microservices වල වාසි (Benefits):
- Scalability: වැඩිපුර request එන service එක විතරක් scale කරන්න පුළුවන්. මුළු application එකම scale කරන්න ඕන වෙන්නේ නැහැ.
- Resilience: එක service එකක් down වුණත්, අනෙක් services වලට බලපාන්නේ නැහැ. මුළු system එකම crash වෙන්නේ නැහැ.
- Independent Deployment: එක් එක් service එක වෙන වෙනම develop කරලා, test කරලා, deploy කරන්න පුළුවන්. ඉක්මනට features release කරන්න පුළුවන්.
- Technology Diversity: එක් එක් service එකට වෙනස් programming languages, frameworks, databases භාවිතා කරන්න පුළුවන්. (උදා: එක service එකක් Java වලින්, තව එකක් Python වලින්)
- Easier Maintenance: කුඩා codebases නිසා code එක තේරුම් ගන්න, maintain කරන්න ලේසියි.
Microservices වල අභියෝග (Challenges):
- Complexity: Distributed system එකක් handle කිරීමේදී complexity එක වැඩියි. Service discovery, configuration management, distributed tracing වගේ දේවල් manage කරන්න වෙනවා.
- Data Consistency: Services වලට තමන්ගේම databases තියෙන නිසා, data consistency maintain කිරීම අභියෝගයක්.
- Operational Overhead: වැඩිපුර infrastructure, monitoring, logging tools ඕන වෙනවා.
Monolithic Architecture එකකදී නම්, application එකේ හැම functionality එකක්ම එකම codebase එකක, එකම deployable unit එකක් විදිහට තියෙන්නේ. මේක පොඩි applications වලට හොඳ වුණත්, application එක ලොකු වෙනකොට develop කරන්න, maintain කරන්න අමාරු වෙනවා. ඒක තමයි Microservices වලට මාරු වෙන්න ප්රධානම හේතුවක් වෙන්නේ.
2. Domain-Driven Design (DDD) සහ Microservices: නිවැරදි සීමා හඳුනාගනිමු
Microservices Design කරනකොට වැදගත්ම දේ තමයි, services වල සීමා (boundaries) කොතැනින්ද කියලා තීරණය කරන එක. මෙතැනට තමයි Domain-Driven Design (DDD) කියන methodology එක එන්නේ. DDD කියන්නේ business domain එක හොඳට තේරුම් අරගෙන, ඒ knowledge එක code එකට align කරන approach එකක්.
DDD හි මූලික සංකල්ප (Core Concepts of DDD):
- Domain: අපි develop කරන business area එක (උදා: E-commerce, Banking). Domain එකේ තියෙන business rules, processes හොඳට තේරුම් ගැනීම වැදගත්.
- Ubiquitous Language: Business experts ලා, developers ලා, architects ලා වගේ හැමෝම එකම භාෂාවකින් කතා කරන එක. මේකෙන් misunderstanding අඩු වෙනවා. (උදා: 'Order', 'Product', 'Customer' කියන වචන වල තේරුම හැමෝටම එකම වෙන්න ඕන).
- Bounded Context: මේක තමයි Microservices වලට DDD වලින් ලැබෙන ලොකුම දායකත්වය. Bounded Context එකක් කියන්නේ, domain එකේ නිශ්චිත කොටසක්, ඒ කොටසට අදාළ model එකක් සහ Ubiquitous Language එකක් තියෙන. එක් එක් Microservice එකක් තනි Bounded Context එකක් හෝ Bounded Context කිහිපයක් නියෝජනය කරන්න පුළුවන්. මේකෙන් Services අතර තියෙන dependencies අඩු වෙනවා.උදාහරණයක් විදිහට, E-commerce System එකක:
Order Management
Bounded Context එකක්,Product Catalog
Bounded Context එකක්,Customer Support
Bounded Context එකක් වගේ හඳුනාගන්න පුළුවන්. මේ සෑම Bounded Context එකක්ම වෙනම Microservice එකක් වෙන්න පුළුවන්. - Entities: Identity එකක් (unique ID) තියෙන objects. (උදා:
Customer
,Product
,Order
). Entity එකක state එක කාලයත් එක්ක වෙනස් වෙන්න පුළුවන්. - Value Objects: Identity එකක් නැති objects. ඒගොල්ලන්ගේ වටිනාකම තීරණය වෙන්නේ ඒගොල්ලන්ගේ attribute values වලින්. (උදා:
Address
,Money
,DateRange
). Value Objects Immutable විය යුතුයි. - Aggregates: Entities සහ Value Objects එකට එකතු වෙලා, තනි unit එකක් විදිහට හැසිරෙන cluster එකක්. Aggregate එකකට Aggregate Root එකක් තියෙනවා. මේ Root එක හරහා තමයි Aggregate එකේ තියෙන අනිත් objects වලට access කරන්න පුළුවන්. Transactional consistency maintain කරන්න Aggregates වැදගත් වෙනවා.උදාහරණයක් විදිහට,
Order
එකක් කියන්නේ Aggregate Root එකක් වෙන්න පුළුවන්. ඒOrder
එක ඇතුලේOrderItems
කියන Value Objects හෝ Entities ගොඩක් තියෙන්න පුළුවන්.
DDD මගින් අපිට පුළුවන්, business functionality එකේ core එකට අනුව services Design කරන්න. ඒ කියන්නේ, business process එකට අනුව services වෙන් කරනවා මිසක්, technical layers (උදා: UI service, Business Logic service, Data Access service) අනුව නෙවෙයි.
3. Spring Boot සමඟ Microservices ගොඩනැගීම: වේගවත් සංවර්ධනය
Java වලින් Microservices develop කරනකොට Spring Boot කියන්නේ අනිවාර්යයෙන්ම භාවිතා කළ යුතු Framework එකක්. Spring Boot වල ප්රධානම වාසිය තමයි, development process එක simplify කරන එක. Boilerplate code අඩුයි, auto-configuration නිසා ඉක්මනට projects setup කරගන්න පුළුවන්.
Spring Boot වල ප්රධාන වාසි:
- Rapid Application Development: Spring Initializr වගේ tools හරහා ඉක්මනට project structures හදාගන්න පුළුවන්.
- Embedded Servers: Tomcat, Jetty, Undertow වගේ servers built-in නිසා, වෙනම server install කරන්න ඕන නැහැ. JAR file එකක් විදිහට deploy කරන්න පුළුවන්.
- Opinionated Approach: Default configurations ගොඩක් එක්ක එන නිසා, අපි හැමදේම configure කරන්න ඕන නැහැ.
- Production-Ready Features: Health checks, metrics, externalized configuration වගේ features built-in.
Microservices ecosystem එකක් හදනකොට, Spring Boot එක්ක Spring Cloud කියන project එකත් ගොඩක් වැදගත් වෙනවා. Spring Cloud මගින් distributed system patterns (උදා: Service Discovery, Circuit Breakers, API Gateway, Configuration Management) implement කරන්න පහසුකම් සලසනවා.
4. ප්රායෝගික උදාහරණයක්: Order Service Microservice එකක් Design කරමු
අපි හිතමු අපිට E-commerce system එකක් හදන්න තියෙනවා කියලා. ඒකේ Order Management Bounded Context එකට අදාළව Order Service
කියන Microservice එක Design කරන්නේ කොහොමද කියලා බලමු. මේකේදී අපි DDD concepts සහ Spring Boot එකට ගලපලා භාවිතා කරනවා.
Scenario:
Customer කෙනෙක් Product එකක් order කරනවා. මේ Order එක manage කරන්නේ Order Service
එකෙන්. මේ Service එකට Customer details, Product details වෙනත් services වලින් ලබාගන්න වෙන්න පුළුවන්. ඒත් අපි මේ උදාහරණය සරලව තියාගන්න, Order Service එකේ core functionality එකට අවධානය යොමු කරමු.
DDD Application for Order Service:
- Bounded Context:
Order Management
- Ubiquitous Language:
Order
,OrderItem
,Product ID
,Customer ID
,Quantity
,Price
,OrderStatus
. - Aggregate Root:
Order
. Order එක ඇතුලේOrderItems
collection එකක් තියෙනවා.
Spring Boot Project Structure (සරලව):
pom.xml
එකට අවශ්ය dependencies (spring-boot-starter-web
, spring-boot-starter-data-jpa
, h2 database
for simplicity) එකතු කරගන්න.
Code Example 1: Order Aggregate Root (Entity)
Order.java
කියන්නේ අපේ Aggregate Root එක. මේකේ OrderItems
collection එකක් තියෙනවා. මේක JPA @Entity
එකක් විදිහට define කරනවා.
package com.example.orderservice.domain.model;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "orders") // 'order' is a reserved keyword in some DBs
public class Order {
@Id
private String id;
private String customerId;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
public Order() {
this.id = UUID.randomUUID().toString();
this.orderDate = LocalDateTime.now();
this.status = OrderStatus.PENDING;
}
public Order(String customerId) {
this();
this.customerId = customerId;
}
// Method to add items - business logic should be here (DDD!)
public void addItem(String productId, int quantity, BigDecimal price) {
OrderItem item = new OrderItem(this, productId, quantity, price);
this.items.add(item);
}
// Method to calculate total price
public BigDecimal calculateTotalPrice() {
return items.stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// Getters and Setters (omitted for brevity, but crucial for JPA and DTO mapping)
public String getId() { return id; }
public String getCustomerId() { return customerId; }
public LocalDateTime getOrderDate() { return orderDate; }
public OrderStatus getStatus() { return status; }
public List<OrderItem> getItems() { return items; }
public void setStatus(OrderStatus status) { this.status = status; }
// Setters for other fields if needed, but prefer constructors/business methods for state changes
}
// OrderStatus as an Enum
public enum OrderStatus {
PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED
}
// OrderItem as an Entity (part of Order aggregate)
@Entity
@Table(name = "order_items")
class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
private String productId;
private int quantity;
private BigDecimal price;
public OrderItem() {}
public OrderItem(Order order, String productId, int quantity, BigDecimal price) {
this.order = order;
this.productId = productId;
this.quantity = quantity;
this.price = price;
}
// Getters and Setters
public Long getId() { return id; }
public Order getOrder() { return order; }
public String getProductId() { return productId; }
public int getQuantity() { return quantity; }
public BigDecimal getPrice() { return price; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public void setPrice(BigDecimal price) { this.price = price; }
}
Code Example 2: Spring Data JPA Repository
OrderRepository.java
එක Order
Aggregate Root එක manage කරන්න.
package com.example.orderservice.domain.repository;
import com.example.orderservice.domain.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderRepository extends JpaRepository<Order, String> {
// Custom query methods can be added here if needed
}
Code Example 3: Order Service (Business Logic)
OrderService.java
එක business logic handle කරනවා. Repository එකට access කරන්නේ මේ Service එක හරහා.
package com.example.orderservice.application.service;
import com.example.orderservice.domain.model.Order;
import com.example.orderservice.domain.model.OrderStatus;
import com.example.orderservice.domain.repository.OrderRepository;
import com.example.orderservice.presentation.dto.CreateOrderRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class OrderService {
private final OrderRepository orderRepository;
@Autowired
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional
public Order createOrder(CreateOrderRequest request) {
// In a real microservices setup, you'd validate customerId and productId
// by calling Customer Service and Product Service (e.g., via REST or message queues).
// For simplicity, we assume they are valid here.
Order order = new Order(request.getCustomerId());
request.getItems().forEach(itemDto ->
order.addItem(itemDto.getProductId(), itemDto.getQuantity(), itemDto.getPrice())
);
return orderRepository.save(order);
}
@Transactional
public Optional<Order> updateOrderStatus(String orderId, OrderStatus newStatus) {
return orderRepository.findById(orderId).map(order -> {
order.setStatus(newStatus);
return orderRepository.save(order);
});
}
public Optional<Order> getOrderById(String id) {
return orderRepository.findById(id);
}
public List<Order> getAllOrders() {
return orderRepository.findAll();
}
}
Code Example 4: REST Controller (Presentation Layer)
OrderController.java
එක REST API endpoints expose කරනවා.
package com.example.orderservice.presentation.controller;
import com.example.orderservice.application.service.OrderService;
import com.example.orderservice.domain.model.Order;
import com.example.orderservice.presentation.dto.CreateOrderRequest;
import com.example.orderservice.presentation.dto.OrderItemDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
Order createdOrder = orderService.createOrder(request);
return new ResponseEntity<>(createdOrder, HttpStatus.CREATED);
}
@GetMapping("/{id}")
public ResponseEntity<Order> getOrderById(@PathVariable String id) {
return orderService.getOrderById(id)
.map(order -> new ResponseEntity<>(order, HttpStatus.OK))
.orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
@GetMapping
public ResponseEntity<List<Order>> getAllOrders() {
List<Order> orders = orderService.getAllOrders();
return new ResponseEntity<>(orders, HttpStatus.OK);
}
}
// DTO for CreateOrderRequest (simplified)
class CreateOrderRequest {
private String customerId;
private List<OrderItemDto> items;
// Getters and Setters
public String getCustomerId() { return customerId; }
public void setCustomerId(String customerId) { this.customerId = customerId; }
public List<OrderItemDto> getItems() { return items; }
public void setItems(List<OrderItemDto> items) { this.items = items; }
}
// DTO for OrderItem (simplified)
class OrderItemDto {
private String productId;
private int quantity;
private BigDecimal price;
// Getters and Setters
public String getProductId() { return productId; }
public void setProductId(String productId) { this.productId = productId; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
}
Code Example 5: application.properties (H2 Database)
spring.datasource.url=jdbc:h2:mem:ordersdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
server.port=8080
මේ උදාහරණයෙන් පෙන්නන්නේ, DDD මූලධර්ම (Order
Aggregate Root එකක් විදිහට, business logic Order
class එකේ තියෙනවා) සහ Spring Boot (@Entity
, @Repository
, @Service
, @RestController
annotations) කොහොමද එකට වැඩ කරන්නේ කියලා. ඇත්ත System එකකදී, createOrder
එක ඇතුලේ Customer Service
එකට සහ Product Service
එකට Call කරලා, data validate කරගන්න හෝ අලුත් data create කරගන්න පුළුවන්. ඒ වගේම, Microservices අතර communication වලට REST APIs, Message Queues (Kafka, RabbitMQ) වගේ දේවල් භාවිතා කරන්න පුළුවන්.
අවසන් වචනය: Microservices ගමනට සූදානම්ද?
Microservices Architecture එකට මාරු වෙන එක ලේසි ගමනක් නෙවෙයි. ඒත්, නිවැරදිව Design කරලා Implement කරනවා නම්, ඒකෙන් ලැබෙන වාසි (scalability, resilience, independent deployment) හරිම ඉහළයි. මේ Guide එකෙන් අපි Microservices වල මූලික සංකල්ප, Domain-Driven Design වල වැදගත්කම සහ Spring Boot භාවිතා කරලා Practical Microservice එකක් Design කරන හැටි ඉගෙන ගත්තා. DDD මූලධර්ම හරියට තේරුම් අරගෙන, ඔබේ Bounded Contexts නිවැරදිව හඳුනාගන්න පුළුවන් නම්, Microservices වල සාර්ථකත්වය බොහෝ දුරට තීරණය වෙනවා.
මතක තියාගන්න, Microservices කියන්නේ magic bullet එකක් නෙවෙයි. හැම project එකකටම මේක ගැලපෙන්නේ නැහැ. ඒත්, ලොකු, complex applications වලට නම් මේක හොඳම විසඳුමක් වෙන්න පුළුවන්.
ඔබේ අදහස් Comment කරන්න! මේ ගැන වැඩිදුර දැනගන්න ඕන නම් හෝ මේ වගේ තවත් Guides ඕන නම්, අපිට කියන්න. මීළඟ Project එකට මේ Concepts Try කරලා බලන්න!
සුභ Coding!