Come realizzare applicazioni enterprise moderne (backend e frontend)

Volevo condividere le ultime esperienze di best-practice e patterns acquisite negli ultimi anni, lavorando su applicazioni Enterprise con tecnologie Microsoft e con colleghi esperti sia di Backend che di Frontend. Alcune soluzioni risolvono molti dei limiti che avevo riscontrato in precedenti progetti, e aiutano a rendere il codice e la struttura del progetto più semplice da mantenere ed estendere. Questi concetti derivano dall’applicazione di DDD (Domain Driven Design).

Consigli sulla organizzazione di file e cartelle

Il controllo di versione del codice con GIT l’ho trovata molto potente, molto meglio di TFS e SVN, anche se richiede una curva di apprendimento iniziale maggiore.

Innanzitutto l’organizzazione dei file e progetti. Ho trovato molto comoda l’architettura onion (o hexagonal) che consiste in dividere in layers logici e con una semplice regola: gli strati esterni possono dipendere dagli strati inferiori, ma nessun codice nello strato inferiore può dipendere direttamente da qualsiasi codice nel livello esterno. Questo è essenzialmente il principio di “inversione di dipendenza” (DiP), presentato in termini di architettura generale, non solo singole classi.

In pratica i sorgenti saranno divisi in “cartelle” numerate:

  • 00-Documentation, 00-Sql scripts: le parti non connesse direttamente con il codice, quali la documentazione, script sql, etc
  • 01-Core: i tipi di base, o extension methods, o funzionalità condivisibili con tutti i layer, come ad esempio un logger
  • 02-Domain: i tipi di dati relativi al “domain”, come eccezioni, eventi, domain interfaces, model interfaces
  • 03-Service interfaces: le interfacce che verranno implementate dall’infrastruttura o dai servizi
  • 04-Application Services: servizi che utilizzano uno o più servizi di infrastruttura per fornire una domain logic, implementano solo interfacce. Implementano un livello logico più astratto, combinando più servizi di infrastruttura per risolvere problemi complessi (ad esempio un servizio di autenticazione, che utilizza 3/4 servizi di infrastruttura per “astrarre” le complessità specifiche nel livello pubblico (06)
  • 05-Infrastructure Services: servizi che implementano uno specifica funzionalità. Qui ci saranno le comunicazioni con il database o REST Apis (es. sottocartella Infrastructure.DataAccess), con microservices o servizi di terze parti, di solito verso REST Api.
  • 06-Frontend, 06-Clients: questo layer contiene applicazioni SPA (Angular, React, etc), MVC, etc; oppure client che comunicano sul bus (tipo RabbitMQ) che poi utilizzeranno i layer inferiori per la business/domain logic.
  • 07-Tools: qui i nostri tool interni, quindi non pubblicamente disponibili, come invece è il layer 06

Per gli unit-test, in ogni layer, per ogni cartella/progetto potete avere una cartella con suffisso “.Tests” a identificare gli unit test.

Questa struttura aiuterà un nuovo sviluppatore a entrare subito nella logica, e non deve ricordarsi la “posizione” logica di decine di progetti ordinati alfabeticamente, ma che nulla dicono sulle loro dipendenze. In genere – su progetti complessi – ci si può impiegare mesi a ricordarsi il posizionamento logico rispetto ad altre dipendenze, e spesso ho visto includere progetti (da junior developers) in posti che mai avrebbero dovuto esserci, riuscendo quindi a implementare servizi senza utilizzo di interfacce, diventando poi impossibili da testare o estendere/mantenere.

Consigli per implementare il backend

Per progetti non di piccole dimensioni, per l’implementazione del backend, consiglio l’utilizzo di CQRS e Event Sourcing, che permetterà al progetto di essere semplicemente estendibili, mantenibile e sempre logicamente corretto. Solitamente i progetti diventavano un ammasso “di cose” senza più una logicità (specie se molti diversi programmatori ci lavorano nel tempo). Con questi pattern, invece, siamo quasi obbligati a mettere “le cose giuste nel posto giusto”, dando una prospettiva di vita maggiore al progetto e semplificando/velocizzando le richieste future, anche se non minimamente previste.

Consigli per implementare il frontend

Avendo utilizzato Angular e Typescript negli ultimi anni, posso consigliare di seguire le best-practice di John Papa. In particolare ricordo di evitare di implementare logica nei file template (html) in quanto renderebbe molto difficile la parte di unit-testing.

Altre consigli utili riguardano la distribuzione delle cartelle dividendo in:

  • components: i componenti finali che implementano una specifica necessità, di solito non utilizzati direttamente ma tramite containers. Eventuali parametri sono forniti tramite Input, e non tramite services. Suddivisi in cartelle che conterranno un file .ts e un file .html.
  • containers: i componenti di “presentazione” che utilizzano uno o più components per implementare la logica della “pagina”.
    Se non si utilizza Redux, i containers utilizzeranno services per implementare le funzionalità e forniranno tramite “Input” i dati necessari ai components. Anche loro suddivisi in cartelle che conterranno un file .ts e un file .html.
  • services: i servizi che si interfacciano verso REST Api o implementano funzionalità necessarie ai containers.
  • models: i data contract necessari alla applicazione, suddivisi in cartelle in base al raggruppamento logico.

Per progetti reali consiglio anche l’adozione di Redux, che – come per CQRS e ES per il backend – separa la domain logic dall’implementazione.

ngrx redux pattern diagram

Per sapere di più su Redux, vedete il mio precedente articolo: Gestione dello stato dell’applicazione con il pattern Redux in angular.

In quel caso avremo ulteriori cartelle:

  • actions: le azioni divise logicamente per file. Per i nomi delle actions, consiglio lo standard utilizzato anche nella messaggistica (tipo RabbitMQ), quindi verbo al passato per gli eventi, e verbo al presente per i comandi/richieste.
    Ad esempio un comando che richiede una azione di backend (UserCreateRequestAction) implementa due ulteriori actions (UserCreateFailedAction e UserCreatedAction)
  • effects: l’implementazione verso il backend, utilizzano i services ed emettono nuove actions
  • reducers: l’implementazione dello store in base alle actions

 

One Comment

Add a Comment

Il tuo indirizzo email non sarà pubblicato.