Come districare una classe di dati in cui si mescolano le annotazioni di diversi framework?

5

Il problema del mix di framework

Utilizzo due framework: SCI 2 SDK di PingIdentity e Spring LDAP - per deserializzare una risorsa SCIM (cioè JSON) su un oggetto Java quindi scriverlo in una directory LDAP e viceversa. Il problema è che entrambi i framework vogliono annotare i campi della mia classe di dati Java e questo sta diventando molto complicato. Esiste un buon progetto per dividere le richieste straniere sulla mia classe - provenienti dai framework - in modo che non si mescolino o addirittura si creino conflitti in una classe? Immagino che questo caso di "JSON < - > object < - > storage" transfer sia piuttosto comune ...

Esempio di codice

Facciamo del mio esempio più concreto con un po 'di codice. Innanzitutto, ecco i requisiti sulla mia classe, diciamo AppleSauce , imposti dai framework:

  1. La classe AppleSauce deve estendere com.unboundid.scim2.common.BaseScimResource .
  2. La classe AppleSauce deve essere annotata con @com.unboundid.scim2.common.annotations.Schema .
  3. I campi di AppleSauce che dovrebbero apparire nella serializzazione JSON devono essere annotati con @com.unboundid.scim2.common.annotations.Attribute .
  4. La classe AppleSauce deve essere annotata con @org.springframework.ldap.odm.annotations.Entry (in realtà non è un problema, ma menzionato per completezza).
  5. I campi di AppleSauce che devono essere scritti negli attributi LDAP devono essere annotati con @org.springframework.ldap.odm.annotations.Attribute .
  6. AppleSauce deve contenere un campo di tipo javax.naming.Name annotato con @org.springframework.ldap.odm.annotations.Id .
  7. Sia SCIM 2 SDK che Spring LDAP vogliono AppleSauce per essere un JavaBean mutabile con getter e setter per tutti i campi ad essi relativi. I voglio AppleSauce per essere un oggetto valore immutabile.

Ecco come appare AppleSauce con un solo campo interessante email (la mia vera classe ha circa 45 campi):

// imports omitted

@com.unboundid.scim2.common.annotations.Schema(id = "MY_SCHEMA_URN", name = "AppleSauce", description = "delicious stuff to go with cake")
@org.springframework.ldap.odm.annotations.Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }, base = "ou=sauces,dc=example,dc=org")
public class AppleSauce extends com.unboundid.scim2.common.BaseScimResource {
  @org.springframework.ldap.odm.annotations.Id javax.naming.Name dn;

  @com.unboundid.scim2.common.annotations.Attribute(description = "email address", multiValueClass = String.class, isRequired = true)
  @org.springframework.ldap.odm.annotations.Attribute(name = "mail", syntax = "1.3.6.1.4.1.1466.115.121.1.26{256}")
  private List<String> email;

  // getters and setter omitted
}

Una possibile soluzione

Il mio pensiero iniziale è quello di estrarre un'interfaccia contenente tutti i getter (questo ha anche il vantaggio di fornire una vista di sola lettura della classe) e due classi che la implementano, ciascuna contenente gli stessi campi con annotazioni per i rispettivi framework. Qualcosa del genere:

public interface AppleSauce {
  List<String> getEmail();
}

@com.unboundid.scim2.common.annotations.Schema(id = "MY_SCHEMA_URN", name = "AppleSauce", description = "delicious stuff to go with cake")
public class ScimAppleSauce extends com.unboundid.scim2.common.BaseScimResource implements AppleSauce {

  @com.unboundid.scim2.common.annotations.Attribute(description = "email address", multiValueClass = String.class, isRequired = true)
  private List<String> email;

  @Override
  List<String> getEmail() {
    return this.email;
  }
}

@org.springframework.ldap.odm.annotations.Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }, base = "ou=sauces,dc=example,dc=org")
public class LdapAppleSauce implements AppleSauce {
  @org.springframework.ldap.odm.annotations.Id javax.naming.Name dn;

  @org.springframework.ldap.odm.annotations.Attribute(name = "mail", syntax = "1.3.6.1.4.1.1466.115.121.1.26{256}")
  private List<String> email;

  @Override
  List<String> getEmail() {
    return this.email;
  }
}

Lo svantaggio di questo approccio è che richiede codice aggiuntivo per convertire le istanze di ScimAppleSauce in LdapAppleSauce (copia di molti campi) e viceversa, perché nessuno dei due framework funzionerà correttamente con il tipo AppleSauce . Non sono troppo preoccupato per l'etichetta getter / setter grazie alle annotazioni Project Lombok , ma copiare manualmente 45 campi è una prospettiva molto noiosa e soggetta a errori .

Difficoltà di bonus

Naturalmente, c'è dell'altro in questa storia ...

  1. I campi della classe AppleSauce riportano anche le annotazioni javax.validation , aggiungendo ai campi disordine. Mettere queste annotazioni sui getter nell'interfaccia AppleSauce sarebbe una buona idea?
  2. Che cosa succede se ho bisogno di serializzare AppleSauce in un diverso formato JSON come un semplice JSON senza SCIM? Potrebbe esserci una terza implementazione di AppleSauce , JsonAppleSauce , ma poi il numero di convertitori esplode.
  3. Non mi piace la mutabilità di JavaBeans e preferirei che tutti gli oggetti fossero immutabili.

Esiste un design pulito per risolvere questo problema di "framework mix"?

    
posta Kolargol00 27.08.2018 - 14:49
fonte

3 risposte

1

Suggerirei di mantenere il singolo oggetto disordinato sul livello dell'API (magari aggiungerlo come prefisso all'API o suffisso con JSON o JO?) e quindi avere il modello di dominio classe separatamente con cui il sistema funziona.

Utilizzando questo approccio, se si desidera avere una classe e-mail per i campi di posta elettronica, ad esempio, è possibile farlo. E la convalida sull'e-mail potrebbe essere eseguita all'interno della classe Email.

Capisco che potrebbe sembrare che tu stia ripetendo te stesso, ma puoi facilmente usare un bean mapper per passare senza problemi da un oggetto all'altro e finire con un oggetto dominio validato pulito.

    
risposta data 12.12.2018 - 15:13
fonte
0

Penso che l'approccio migliore potrebbe essere quello di progettare un linguaggio specifico del dominio (DSL) per descrivere queste risorse serializzabili. Il parser per questo DSL può generare una rappresentazione intermedia. È quindi possibile implementare alcuni semplici generatori di codice che prendono la rappresentazione intermedia e generano classi Java correttamente annotate:

DSL ---> IR --+-> SCIM2 Code Generator ---> SCIM2 annotated class
              |
              +-> LDAP Code Generator ---> LDAP annotated class

Ad esempio, è possibile utilizzare JavaCC. JavaCC genererà un parser in Java, che è possibile definire in modo tale che contenga un metodo parse() che restituisce List<Resource> . Resource qui è un oggetto che definisci, che può contenere un elenco di campi, i loro tipi, ecc. Resource ha solo bisogno di informazioni sufficienti per essere serializzato nella classe Java annotata.

Dovresti essere in grado di integrare facilmente la generazione del codice con elementi come Maven e IDE come Intellij possono essere configurati per riconoscere le sorgenti generate. Ciò significa che le classi generate verranno riconosciute dopo aver provato a utilizzarle nel resto del codice in un secondo momento.

    
risposta data 29.08.2018 - 16:52
fonte
0

Una possibile soluzione sarebbe quella di seguire una Architettura pulita approccio.

Avresti la tua classe di entità AppleSauce priva di annotazioni quadro di qualsiasi tipo, ad esempio un POJO semplice. Quindi, avresti una classe AppleSauce diversa per ogni framework, contenente solo le annotazioni necessarie per il framework corrispondente, insieme alle classi mapper che le mapperebbero alla classe entità.

Se sei disposto a convivere con il fatto che il numero di classi potrebbe iniziare a crescere rapidamente, ottieni i vantaggi delle classi di Single Responsibility e del codice coesivo e disaccoppiato.

    
risposta data 12.12.2018 - 17:24
fonte

Leggi altre domande sui tag