Dopo aver affrontato gli aspetti teorici della modellazione delle entità che partecipano all‘applicazione, passiamo alla fase di progettazione dello strato di persistenza. Dal metamodello alle tabelle del database alle classi persistenti.
Introduzione
Dopo aver affrontato gli aspetti teorici della modellazione delle entità che partecipano alla applicazione, è giunto il momento di passare alla fase di progettazione dello strato di persistenza. Riconsiderando il metamodello di alto livello introdotto negli articoli precedenti (si veda a tal proposito la figura 1) cercheremo di vedere il modo in cui questo modello possa essere utilizzato come punto di partenza per la progettazione sia del database che degli oggetti Java che dovranno essere resi persistenti e che saranno utilizzati dallo strato di business logic.
Figura 1 – Il grafo delle entità che definiscono il modello del dominio applicativo.
Il diagramma riportato nella figura 1 rappresenta il reticolo di entità che sono state individuate come parti componenti del dominio applicativo su cui il nostro ipotetico software dovrà agire. In questa fase, ossia prima di passare alla fase implementativa, il diagramma rappresenta ancora entità astratte: non abbiamo ancora nessuna corrispondenza, ne’ con le classi ne’ con le tabelle del database, che sono invece i due prodotti che otterremo a monte di una ulteriore fase di indagine e progettazione. Attraverso alcune ulteriori operazioni di analisi si arriverà a una formalizzazione più puntuale e dettagliata in modo da disegnare il database, le interfacce, i messaggi del sistema dei componenti. Nel caso specifico vedremo come, per ogni relazione che si instaura fra entità differenti, sia possibile arrivare alla creazione delle tabelle dove memorizzare tali entità e come contemporaneamente sia possibile creare dei beans persistenti su tali tabelle. Per quello che concerne la parte di persistenza faremo riferimento al modello JPA, tipico degli entity EJB 3.0 che si basa sull’uso di annotazioni apposite e di un motore di persistenza sottostante (in questo caso Hibernate o Toplink). Per chi necessitasse di maggiori approfondimenti in tale ambito, si rimanda ai riferimenti al termine dell’articolo.
Nel tentativo di limitare l’estensione di questo articolo, analizzeremo solamente alcune parti del diagramma delle entità presentato in figura 1, cercando di identificare le parti più interessanti o significative per gli scopi didattici. In particolare verranno analizzate le seguenti relazioni:
- apiario-arnia-famiglia (che come vedremo è simile alla relazione Family-Queen)
- famiglia-scheda
- apiario-apicoltore (relazione qualificata con entità “intervento”)
Vedremo in questo articolo alcune delle relazioni più semplici, mentre nel prossimo concentreremo l’attenzione su alcuni aspetti più avanzati delle tecniche di modellazione.
Modellare la relazione Apiario-Arnia-Famiglia
Analogamente alle prime operazioni che un apicoltore svolge sul campo (per prima cosa sceglie un posto dove posizionare le proprie arnie), anche il nostro ipotetico software potrebbe partire dalla creazione della entità apiario per cui possiamo iniziare a modellare le prime relazioni che si instaurano con tale entità. Nel caso specifico la relazione Apiario-Arnia-Famiglia è piuttosto interessante perche’ permette di introdurre un primo concetto piuttosto importante: dato che la relazione Arnia-Famiglia è di tipo 1-a-1 in forma molto legante (per l’apicoltore è più importante considerare la famiglia che non la cassetta in legno che la contiene) possiamo effettuare una approssimazione (certamente molto forte). Questa approssimazione prevede di accorpare le due entità e trasformare le caratteritiche della arnie in attributi della famiglia: il colore, il tipo della cassetta in legno (p.e.: Dadant Blatt, adatta ad apicoltura stanziale, oppure la Kubic, più adatta per il nomadismo), così come la posizione della cassetta nell’apiario possono proprio diventare attributi dell’entità Famiglia.
Dovendo effettuare un accorpamento, la relazione che si instaura fra una Famiglia e una Arnia è di composizione: infatti la composizione è una relazione più forte della semplice aggregazione e quindi la relazione finale prende spunto dalla relazione che sussiste fra Arnia e Famiglia a scapito di Apiario-Arnia.
Ovviamente questa operazione di accorpamento non sarebbe perseguibile se ad esempio volessimo tenere traccia di ogni singolo materiale di proprietà dell’apicoltore (esigenza in realtà piuttosto frequente). Per permettere di semplificare il modello, ma al tempo stesso permettere la gestione anche di questa funzionalità, si potrebbe pensare di associare la famiglia direttamente all’apiario (informazione importante ai fini della gestione delle api) e di spostare l’entità Arnia in una altra posizione, magari associandola alla entità MaterialeMagazzino.
In questo caso quindi procediamo a questo semplice refactoring dando così modo al lettore di verificare con mano un evento che si verifica piuttosto di frequente: il cambio dei requisiti in corso d’opera.
Considerando quindi la relazione Apiario-Famiglia si tratta di una relazione bidirezionale con una cardinalità 1-a-n in cui la associazione è di composizione (infatti un’arnia non può vivere al di fuori di un apiario, può appartenere a un apiario per volta, mentre un apiario senza le arnie non è altro che… un prato). Questa considerazione avrà una importante ripercussione nella parte di progettazione delle classi persistenti.
Per quello che concerne invece il database possiamo pensare a una implementazione semplificata: sebbene non vi sia molto da dire per quello che riguarda il mapping delle due entità, che verranno mappate in due tabelle corrispondenti, per quello che riguarda la definizione della relazione possiamo pensare ad una gestione semplificata. Avere una chiave esterna (Foreign Key, FK) in entrambe le tabelle comporta una serie di problemi legati al modello “nasce prima l’uovo o la gallina?”: spesso FK vuol dire vincolo NOT-NULL sulla colonna e quindi questo può portare un sistema più difficile da gestire (ovvero una business logic più complessa per la gestione delle operazioni di CRUD sulle entità). La relazione verrà quindi gestita nel database con una sola chiave esterna nella tabella Family (ovvero dal lato “n” della cardinalità), cosa che poi dovrà essere opportunamente gestita nella fase di definizione degli entiti bean.
Il database quindi si popola delle seguenti tabelle:
Figura 2 – La tabella Apiary.
Figura 3 – La tabella Family.
Si noti come nella tabella Family siano presenti le colonne relative alle chiavi esterne (per adesso ci interessa solamente la colonna apiary_id) e che la chiave sia gestita in modo automatico tramite un generatore incrementale (approccio seguito per tutte le en tità del sistema, si veda oltre).
Per quello che concerne invece il modello EJB questa relazione può essere modellata grazie alla definizione dei due bean Family e Apiary. Gli attributi di persistenza verranno mappati in modo diretto sulle relative colonne delle due tabelle. Il bean Apiary in particolare potrebbe essere:
@Entity @Table(name = "apiary") @NamedQueries({@NamedQuery(name = "Apiary.findById", query = "SELECT a FROM Apiary a WHERE a.id = :id"), @NamedQuery(name = "Apiary.findByLocation", query = "SELECT a FROM Apiary a WHERE a.location = :location"), @NamedQuery(name = "Apiary.findByName", query = "SELECT a FROM Apiary a WHERE a.name = :name"), @NamedQuery(name = "Apiary.findByDescription", query = "SELECT a FROM Apiary a WHERE a.description = :description")}) public class Apiary implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @Column(name = "location") private String location; @Column(name = "name") private String name; @Column(name = "description") private String description; @OneToMany(mappedBy = "apiary") private Collection familyCollection; public Apiary() { } public Apiary(Integer id) { this.id = id; } // segue elenco dei metodi get/set }
Per il momento tralasciamo la query in EJBQL delle quali parleremo in seguito e ci concentreremo invece su due aspetti interessanti per quello che concerne la definizione delle regole di persistenza: da un lato si può notare come la chiave primaria (campo id) sia mappata nel database tramite la strategia di generazione automatica (in questo caso si usa un contatore progressivo); questa modalità si ottiene con la annotazione
@GeneratedValue(strategy = GenerationType.AUTO)
L’altro fattore interessante è dato dall’annotazione
@OneToMany(mappedBy = "apiary") private Collection familyCollection;
che consente da un lato di mantenere la relazione a livello di oggetti (infatti la classe Apiary contiene un riferimento a una collezione di oggetti Family) ma che contemporaneamente si preoccupa di specificare che la relazione verrà “mantenuta” dall’altra parte della relazione, ossia che la persistenza verrà salvata sul database in virtù di quanto specificato nell’attributo apiary della classe Family. Per maggior chiarezza di seguito è riportato il codice della classe corrispondente:
@Entity @Table(name = "family") @NamedQueries({@NamedQuery(name = "Family.findById", query = "SELECT f FROM Family f WHERE f.id = :id"), @NamedQuery(name = "Family.findByPosition", query = "SELECT f FROM Family f WHERE f.position = :position")}) public class Family implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; // la posizione della arnia entro cui la famiglia è contenuta @Column(name = "position") private Integer position; // campo che contiene le note per la famiglia @Lob @Column(name = "notes") private String notes; // relazione con l'apiario al quale la famiglia appartiene @JoinColumn(name = "apiary_id", referencedColumnName = "id") @ManyToOne private Apiary apiary; // campo di relazione con la regina che vive in questa famiglia @JoinColumn(name = "queen_id", referencedColumnName = "id") @OneToOne private Queen queen; // campo di relazione con il report @JoinColumn(name = "report_id", referencedColumnName = "id") @ManyToOne private Report report; public Family() { } public Family(Apiary a) { this.apiary = a; } // seguono i metodi get/set }
Anche in questo caso si può notare la definizione della strategia di assegnazione della chiave, ma è più importante la annotazione che permette di specificare la relazione: in questo caso la classe Family si prende la responsabilità di salvare la relazione con l’entità Apiary.
La caratteristica della relazione di composizione fra la famiglia e l’apiario si può tradurre nel fatto che una famiglia non può vivere al di fuori di un apiario: il costruttore di Family quindi ha fra i suoi parametri anche un riferimento all’oggetto Apiary. Il costruttore di default (che di fatto viola tale indicazione del modello) è necessario per rispetto delle specifiche JavaBeans.
Conclusioni
Per questo mese ci fermiamo qui: proseguiremo il mese prossimo parlando delle relazioni che sono rimaste in sospeso affrontando alcune casistiche piuttosto importanti. Successivamente affronteremo la definizione dello strato di business logic e la creazione dello strato client.
Riferimenti
[EJB-a] “EJB 3.0”
http://java.sun.com/products/ejb
[EJB-b] G. Puliti, “Enterprise Java Beans 3.0”, MokaByte 111, ottobre 2006
https://www.mokabyte.it/cms/article.run?articleId=3VL-HPK-X3O-RI2_7f000001_30520983_55d21c14
[FOW] Martin Fowler, Kendall Scott, “UML Distilled”, Addison-Wesley Professional
[LVT] Luca Vetti Tagliati, “UML e ingegneria del software”, Tecniche Nuove
[LAR] Craig Larman, “UML and Design Patterns”, Prentice Hall PTR
[A2] “Apicoltura 2000 – portale di apicoltura”
http://www.apicoltura2000.it/
[MDI] “Mieli d’Italia: sito della Unione Nazionale Associazioni Apicoltori Italiani”
http://www.mieliditalia.it
Giovanni Puliti ha lavorato per oltre 20 anni come consulente nel settore dell’IT e attualmente svolge la professione di Agile Coach. Nel 1996, insieme ad altri collaboratori, crea MokaByte, la prima rivista italiana web dedicata a Java. Autore di numerosi articoli pubblicate sia su MokaByte.it che su riviste del settore, ha partecipato a diversi progetti editoriali e prende parte regolarmente a conference in qualità di speaker. Dopo aver a lungo lavorato all’interno di progetti di web enterprise, come esperto di tecnologie e architetture, è passato a erogare consulenze in ambito di project management. Da diversi anni ha abbracciato le metodologie agili offrendo ad aziende e organizzazioni il suo supporto sia come coach agile che come business coach. È cofondatore di AgileReloaded, l’azienda italiana per il coaching agile.