Passa a:
Architetture Esagonali: una nuova prospettiva con ports and adapters

Architetture Esagonali: una nuova prospettiva con ports and adapters

L’evoluzione dell’ingegneria del software ha portato alla nascita di paradigmi architetturali sempre più sofisticati, tra cui spicca l’architettura esagonale, introdotta da Alistair Cockburn nel 2005.

Questo approccio innovativo, noto anche come pattern ‘Ports and Adapters’, rappresenta una risposta alle limitazioni delle tradizionali architetture a livelli e offre una soluzione elegante per costruire sistemi software robusti e facilmente mantenibili.

Le architetture tradizionali a tre livelli (presentazione, business logic e persistenza) hanno dominato il panorama dello sviluppo software per decenni ma questo approccio presenta significative limitazioni strutturali che ne compromettono la flessibilità e la testabilità. La dipendenza gerarchica dall’alto verso il basso crea un forte accoppiamento tra i componenti, rendendo il software rigido e difficile da modificare.

Quando la logica di business si infiltra nei livelli di presentazione o persistenza, emerge il problema della duplicazione del codice e della difficoltà di testing, compromettendo la qualità complessiva del sistema.

L’architettura esagonale ribalta questa prospettiva, proponendo un modello concentrico che pone al centro la logica di dominio, protetta dalle influenze esterne. Questa visione inside-out elimina la necessità di pensare in termini di ‘sopra’ e ‘sotto’, concentrandosi invece sulla separazione netta tra il cuore dell’applicazione e il mondo esterno. Il cuore dell’architettura esagonale è costituito da tre elementi fondamentali che lavorano in sinergia per garantire un disaccoppiamento efficace.

Il dominio o ‘domain’ rappresenta il nucleo centrale dell’applicazione, contenendo esclusivamente, ad esempio, le entità, le interfacce dei servizi e i validatori. Questo livello non contiene alcuna logica implementativa, ma solo le definizioni pure che stabiliscono le regole del gioco del business.

Un esempio pratico di definizione nel dominio potrebbe essere:


// domain/user/index.ts
export interface UserService {
  createUser(userData: CreateUserRequest): Promise<User>;
  findUserById(id: string): Promise<User | null>;
  validateUser(user: User): ValidationResult;
}

export interface CreateUserRequest {
  email: string;
  password: string;
  name: string;
}

export interface User {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
}

Attorno al dominio si sviluppa il livello applicativo o ‘application’, che rappresenta il vero cervello del sistema.

Qui risiede la business logic, l’orchestrazione dei casi d’uso e la logica che determina come i diversi componenti devono interagire per raggiungere gli obiettivi prefissati. Questo livello dipende esclusivamente dalle interfacce definite nel dominio, mantenendo la propria indipendenza dalle implementazioni concrete.

Nel configuratore dell’applicazione, i servizi vengono istanziati e resi disponibili:


// application/configurator.ts
import { UserService } from '../domain/user';
import { DrizzleUserService } from '../adapters/drizzle/user';

export class ServiceConfigurator {
  private userService: UserService;

  constructor() {
    this.userService = new DrizzleUserService();
  }

  getUserService(): UserService {
    return this.userService;
  }

}

La corona esterna è formata dagli adattatori o ‘adapter’, i ponti che collegano l’applicazione al mondo esterno.

Questi componenti si occupano di tradurre le richieste dell’applicazione in chiamate concrete verso database, servizi di autenticazione, API esterne e altri sistemi. Gli adapter implementano le interfacce definite nel dominio, garantendo che il cambiamento di una tecnologia esterna non comprometta la stabilità del sistema. Un esempio di adapter concreto per la gestione degli utenti con database potrebbe essere:


// adapters/drizzle/user/index.ts
import { UserService, CreateUserRequest, User } from '../../../domain/user';
import { db } from '../connection';

export class DrizzleUserService implements UserService {
  async createUser(userData: CreateUserRequest): Promise<User> {
    const newUser = await db.insert(usersTable).values({
      email: userData.email,
      password: hashPassword(userData.password),
      name: userData.name,
      createdAt: new Date()
  }).returning();

  return newUser[0];
}

  async findUserById(id: string): Promise<User | null> {
    const users = await db.select().from(usersTable).where(eq(usersTable.id, id));
    return users[0] || null;
  }

  validateUser(user: User): ValidationResult {
    // Implementation of validation
    return { isValid: true, errors: [] };
  }

}

Il meccanismo di funzionamento dell’architettura esagonale si basa sul concetto di porte e adattatori. Le porte fungono da punti di ingresso tecnologicamente neutri, definendo le interfacce che permettono la comunicazione tra il nucleo dell’applicazione e il mondo esterno. Gli adattatori implementano queste porte utilizzando tecnologie specifiche, fungendo da traduttori tra il linguaggio del dominio e quello delle tecnologie esterne.

Un esempio di utilizzo nell’API potrebbe essere:


// application/api/users/index.tsx
import { ServiceConfigurator } from '../../configurator';

const configurator = new ServiceConfigurator();
const userService = configurator.getUserService();

export async function POST(request: Request) {
  const userData = await request.json();

  try {
    const newUser = await userService.createUser(userData);
    return Response.json(newUser, { status: 201 });
  } catch (error) {
    return Response.json({ error: 'Failed to create user' }, { status: 500 });
  }
}

L’adozione dell’architettura esagonale porta benefici concreti e misurabili. La testabilità del software migliora drasticamente grazie al disaccoppiamento intrinseco tra i componenti. È possibile testare la logica di business in completo isolamento, utilizzando mock degli adapter per simulare le dipendenze esterne. La manutenibilità del codice aumenta significativamente grazie alla chiara separazione delle responsabilità, mentre la scalabilità del sistema beneficia della possibilità di sostituire o aggiornare singoli componenti senza impattare l’intera architettura.

L’implementazione dell’architettura esagonale richiede un investimento iniziale in termini di progettazione e sviluppo, ma rappresenta uno strumento potente per costruire sistemi software che privilegiano la flessibilità, la testabilità e la manutenibilità. La sua adozione richiede un cambio di mentalità che sposta l’attenzione dalla tecnologia al dominio, creando applicazioni che riflettono fedelmente le esigenze del business e si adattano naturalmente ai cambiamenti del mercato.

Per saperne di più: https://alistair.cockburn.us/hexagonal-architecture

Ranieri Vannozzi

L'autore

Ranieri Vannozzi

Sviluppatore e consulente software freelance

Scrivo codice dal 2008 e lavoro su sviluppo enterprise, architetture cloud-native e leadership tecnica. Su HeroHubs racconto quello che incontro ogni giorno nei progetti, tra codice, architetture e intelligenza artificiale.

Articolo scritto con il supporto dell'AI e rivisto dall'autore.

Post Correlati

Canary vs Blue Green: come eliminare il downtime

Architettura

Marzo 2026

Canary vs Blue Green: come eliminare il downtime

Guida sulle strategie di rilascio software: scopri come Blue Green e Canary deployment possono proteggere il tuo business e migliorare l'esperienza utente.

Agent as a Service (AaaS): il futuro oltre il SaaS

Architettura

Agosto 2025

Agent as a Service (AaaS): il futuro oltre il SaaS

Scopri il modello Agent as a Service (AaaS): come gli agenti AI autonomi stanno rivoluzionando il software, superando il SaaS e trasformando processi e business.

SST: il futuro del serverless su AWS

Architettura

Luglio 2025

SST: il futuro del serverless su AWS

Scopri SST (Serverless Stack): il framework che rivoluziona lo sviluppo serverless su AWS. Vantaggi, esperienza d'uso, scalabilità e best practice per progetti cloud-native.

Firebase real-time database: la scelta giusta per applicazioni in tempo reale?

Architettura

Giugno 2025

Firebase real-time database: la scelta giusta per applicazioni in tempo reale?

Scopri i vantaggi, i limiti e le alternative di Firebase real-time database: guida completa per scegliere la soluzione ideale per app e piattaforme in tempo reale.

LinkedIn e Apache Kafka: 7 trilioni di messaggi al giorno

Architettura

Giugno 2025

LinkedIn e Apache Kafka: 7 trilioni di messaggi al giorno

Scopri come LinkedIn utilizza Apache Kafka per gestire 7 trilioni di messaggi al giorno: architettura, scalabilità, innovazioni open-source e best practice per big data in tempo reale.

Software architecture: quale usare?

Architettura

Maggio 2025

Software architecture: quale usare?

Guida alle principali architetture software: vantaggi, svantaggi e casi d'uso di client-server, layered, microservizi, event-driven, serverless e altri modelli per progetti moderni.

Microservizi o monolite: dibattito ancora attuale?

Architettura

Marzo 2025

Microservizi o monolite: dibattito ancora attuale?

Microservizi vs monolite: vantaggi, svantaggi e scenari d'uso per scegliere l'architettura software più adatta al tuo progetto nel 2025.

AWS, Cloudflare, Supabase e gli altri: quale scegliere?

Architettura

Febbraio 2025

AWS, Cloudflare, Supabase e gli altri: quale scegliere?

Guida alla scelta del provider cloud ideale: confronto tra AWS, Cloudflare, Supabase, Google Cloud, Azure, DigitalOcean, Vercel, Netlify e Heroku per ogni esigenza di progetto.

Le fallacie dei sistemi distribuiti

Architettura

Gennaio 2025

Le fallacie dei sistemi distribuiti

Scopri le principali fallacie dei sistemi distribuiti e come evitarle: resilienza, sicurezza, scalabilità e best practice per progettare architetture moderne e affidabili.