With Spring Data (and Spring Data JDBC supports it as well) we can very easy implement some DDD concepts in a 1-2-3 steps
Aggregate Root is something we may update and due to DDD it can emit (Domain Eventsourced) events on different operations (so called commands)...
To make our Customer entity (from simple project) an aggregate is easy:
class Customer extends AbstractAggregateRoot<Customer> { /* ... */ }
NOTE: it's extends AbstractAggregateRoot with itself as a generic typed parameter...
Secondly (in our root aggregate) we need to register event, when something important from prespective our domain (business) is happened...
For erxample on create customer Command, each time when new customer was created, we will register customer created event, whuch eventually will be emitted by Spring Data after entity successfully persisted:
public static Customer createForName(String name) {
// Customer customer = ...
CustomerCreatedEvent event = new CustomerCreatedEvent(customer);
customer.registerEvent(event);
return customer;
}
where CustomerCreatedEvent
definition is very simple:
import org.springframework.context.ApplicationEvent;
public class CustomerCreatedEvent extends ApplicationEvent {
public CustomerCreatedEvent(Object source) {
super(source);
}
}
NOTE: it's simple spring application event
Lastly let's build statistics API to show how many customers with same name where actually created...
@RestController
public class StatisticsResource {
Map<String, AtomicLong> statistics = new ConcurrentHashMap<>();
@GetMapping("/statistics")
public Map<String, AtomicLong> getStatistics() {
return statistics;
}
@EventListener
public void on(CustomerCreatedEvent event) {
Customer newCustomer = (Customer) event.getSource();
updateStatisticsFor(newCustomer);
}
void updateStatisticsFor(Customer newCustomer) {
String name = newCustomer.getName();
statistics.putIfAbsent(name, new AtomicLong(0));
AtomicLong counter = statistics.get(name);
counter.incrementAndGet();
statistics.put(name, counter);
}
}
so lets's run app and after let's create few customers:
http :8002 name=test
http :8002 name=test
http :8002 name="test 2"
now we can check if statistics is working as expected:
http :8002/statistics
HTTP/1.1 200 OK
Content-Length: 21
Content-Type: application/json;charset=UTF-8
# output
{
"test": 2,
"test 2": 1
}
To make our app consistent after reboot, let's introduce statistics reconstruction in StatisticsResource
class:
@RestController
public class StatisticsResource {
private final CustomerRepository customerRepository;
public StatisticsResource(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@PostConstruct
public void reconstruct() {
StreamSupport.stream(customerRepository.findAll().spliterator(), true)
.forEach(this::updateStatisticsFor);
}
// rest without caches...
}
And we are done ;)
- Domain Driven Design with relational Databases and Spring Data JDBC.
- Effective Aggregate Design Part I
- Effective Aggregate Design Part II
- Effective Aggregate Design Part III
- Spring Data JDBC reference
- spring.io/blog introducing-spring-data-jdbc
- spring.io/blog spring-data-jdbc-references-and-aggregates
- Advancing Enterprise DDD
- The Vietnam of Computer Science
- GitHub: schauder/talk-ddd-jdbc