Hexagonal Architecture & DDD
In questo progetto, l'architettura esagonale viene realizzata attraverso tre annotazioni custom che fungono da marcatori semantici,
abbinate alle interfacce del package application e alle loro implementazioni concrete nel package infrastructure (adapter).
common/
└── hexagonal/
├── @InBoundPort
├── @OutBoundPort
└── @Adapter
habitquest/tracking/
├── domain/ ← Core
├── application/ ← Porte (InBound + OutBound)
└── infrastructure/ ← Adapters
| Annotazione | Ruolo | Chi la usa |
|---|---|---|
@InBoundPort |
Espone le operazioni che il dominio mette a disposizione del mondo esterno | Interfacce di servizio (HabitService) |
@OutBoundPort |
Definisce le dipendenze che il dominio richiede all'infrastruttura | Interfacce di repository, notifier, logger |
@Adapter |
Implementazione concreta di una porta, che collega il dominio a una tecnologia specifica | HabitServiceImpl, implementazioni infrastrutturali |
Struttura Esagonale dei Microservizi
Questa architettura è sistematicamente applicata ad in ogni microservizio del progetto. Il pattern da seguire è il seguente:
1. InBoundPort — un'interfaccia @InBoundPort per ogni use-case principale del servizio (es. QuestService, AvatarService), che espone le operazioni sul dominio.
2. OutBoundPort — un'interfaccia @OutBoundPort per ogni dipendenza esterna:
- XyzRepository (persistenza)
- XyzNotifier (messaggistica verso altri servizi)
- XyzRestClient (chiamate HTTP verso altri microservizi)
- XyzLogger (logging disaccoppiato)
3. Adapter — un'implementazione @Adapter @Service dell'InBoundPort, più le implementazioni @Adapter @Component di tutti gli OutBoundPort nel layer infrastrutturale (Notifier, Repository, RestClient, Logger).
4. Observer — un'interfaccia XyzObserver nel dominio e una sua implementazione XyzObserverImpl nel layer application, che fa da dispatcher degli eventi verso il XyzNotifier.
Integrazione con il DDD
Tutta la struttura si appoggia sulle marker interface del DDD definite in common.ddd.
Queste interfacce non aggiungono comportamento, ma rendono espliciti i ruoli dei tipi nel modello:
public interface Aggregate<T> extends Entity<T> { T getId(); }
public interface DomainEvent extends ValueObject {}
public interface Repository {}
public interface Factory {}
public interface ValueObject {}
In generale è presente almeno un Aggregate per ogni microservizio, che permette di accedere a tutti i valori di dominio e contiene il cuore della logica di business.
In più sono presenti numerosi Value Objects, Entities e Domain Events.
Gli eventi di dominio sono tutti Java records immutabili che implementano una stessa interfaccia, ad esempio HabitEvent extends DomainEvent.
Ogni evento trasporta lo stato rilevante al momento della sua creazione.
public interface HabitEvent extends DomainEvent {}
public record HabitCreated(Habit habit, Id<Avatar> avatarId) implements HabitEvent {}
public record HabitUpdated(Habit habit, Id<Avatar> avatarId) implements HabitEvent {}
public record HabitAttended(Habit habit, Id<Avatar> avatarId) implements HabitEvent {}
public record HabitDeleted(Id<Habit> habitId, Id<Avatar> avatarId) implements HabitEvent {}
public record HabitNotAttended(Habit habit, Id<Avatar> avatarId) implements HabitEvent {}