Hexagonal Architecture: Ports & Adapters per servizi realmente sostituibili
Ports & Adapters mette il dominio al centro e rende infrastruttura e integrazioni implementazioni sostituibili di contratti espliciti.
L'idea centrale
La Hexagonal Architecture, spesso chiamata Ports & Adapters, separa la logica di business da ciò che cambia più spesso: database, framework web, broker, protocolli, integration engine e sistemi esterni. Il dominio non «chiama Mirth» e non «legge Mongo»: chiede a una porta di pubblicare un ordine o di recuperare un paziente.
Inbound e outbound ports
Inbound Port: espone un caso d'uso del sistema, per esempio AdmitPatient, PlaceOrder o MergePatient.
Inbound Adapter: REST controller, consumer RabbitMQ, job schedulato o comando CLI che invoca il caso d'uso.
Outbound Port: descrive una dipendenza necessaria al caso d'uso, per esempio PatientRepository, ImagingViewerPort o OrderIntegrationPort.
Outbound Adapter: PostgreSQL, FHIR client, HL7/MLLP, Mirth, VueMotion, DICOMweb o un adapter legacy.
La direzione delle dipendenze
Le dipendenze di compilazione puntano verso il dominio. Il core definisce le interfacce che gli servono; l'infrastruttura le implementa. In questo modo cambiare database o provider non impone di riscrivere le regole clinico-amministrative.
Esempio
Order Management può definire OrderPublisher.publish(OrderPlaced). In un'installazione l'adapter produce HL7 ORM via Mirth; in un'altra usa FHIR ServiceRequest; durante la migrazione un LegacyOrderAdapter può ancora chiamare il sistema precedente. Il dominio dell'ordine resta uguale.
Perché è diverso da una classica architettura a layer
In una struttura Controller → Service → Repository è facile che Service importi client esterni e framework. Nell'architettura esagonale il confine è esplicito e verificabile: il domain module non deve dipendere dagli adapter. Test architetturali possono impedire regressioni.
FHIR al confine
FHIR è molto utile come formato di interoperabilità, ma non deve necessariamente diventare il modello interno di ogni bounded context. Un FhirAdapter può tradurre tra il modello di dominio e Patient, Encounter, ServiceRequest o Observation, mantenendo il core indipendente dalla versione e dai profili richiesti dal singolo contesto di integrazione.
Quando non serve complicare
Per un CRUD molto semplice senza regole e senza probabili sostituzioni, una struttura più diretta può bastare. Ports & Adapters crea valore soprattutto quando il dominio è importante e le dipendenze esterne sono numerose o variabili.
