Modo corretto per separare JPA con livello di servizio

0

Ho due tabelle:

area (
  id int PK autoincrement
  code varchar
)

products (
  id int PK autoincrement
  name varchar
  area_id int PK to AREA
  ...
)

Le classi sono persistenti usando eclipselink in questo modo:

@Entity
class Product {
    ...
    private Long id;
    ...
    private String name;
    ...
    @JoinColumn(name = "area_id", referencedColumnName = "id")
    @ManyToOne
    @Expose
    private Area area;
    ...    
}

Funziona bene. Ma sto creando un servizio di assistenza per un'API, un semplice PUT in link dove i dati JSON dovrebbero essere come questo:

{
  id: xxx
  name: xxxx
  area: xxxx
}

Come puoi vedere, desidero che il campo area venga inviato in modo diverso. ID e nome sono gli stessi, ma in questo caso il campo area è la Stringa che è memorizzata in the table.row area.codice .

Aparently questo non può essere fatto con JPA (ho chiesto questo qui: link ) ma lì, qualcuno ha detto quanto segue:

Don't mix your entity and webservices classes and you wont have problem like this.

Quindi, stavo pensando se dovessi avere due classi Prodotto . Uno per il livello di servizio, quello che il cliente utilizzerà quando chiamano l'API, in questo modo:

public class Product implements Serializable {
    private Long id;
    private String name;
    private String area;
}

E poi quando sto gestendo il metodo PUT / GET, converti questa classe in JPA. qualcosa di simile:

x.jpa.Product jpaProduct = new x.jpa.Product();
jpaProduct.setId(product.getId());
jpaProduct.setName(product.getName());
jpaProduct.getArea().setId( getAreaIdByCode(product.getArea()));
...
m.persist(jpaProduct);
    
posta Mariano L 20.07.2017 - 06:00
fonte

2 risposte

1

Ho risolto questo problema, quindi questo approccio non è necessario.

Ecco come l'ho fatto:

Uso dei trasformatori. Quindi l'area del campo è definita in questo modo:

@Transformation(fetch = FetchType.EAGER, optional = false)
@ReadTransformer(transformerClass = AreaAttributeTransformer.class)
@WriteTransformers({
        @WriteTransformer(
                transformerClass = AreaFieldTransformer.class,
                column = @Column(name = "area_id", nullable = false))
})
@Expose
private String area;

Allora quelle clases funzionano così:

AreaAttributeTransformer

public class AreaAttributeTransformer implements AttributeTransformer {

private AbstractTransformationMapping mapping;

@Override
public void initialize(AbstractTransformationMapping abstractTransformationMapping) {
    this.mapping = abstractTransformationMapping;
}


@Override
public Object buildAttributeValue(Record record, Object o, Session session) {
    for (DatabaseField field : mapping.getFields()) {
        if (field.getName().contains("area_id")) {

            EntityManager em = MyEntityManagerFactory.getENTITY_MANAGER_FACTORY().createEntityManager();
            List results = em.createNamedQuery("Areas.findById")
                    .setParameter("id", record.get(field))
                    .getResultList();
            if (results.size() > 0)
                return ((Area) results.get(0)).getCode();
        }
    }
    return null;
}

}

AreaFieldTransformer

public class AreaFieldTransformer implements  FieldTransformer {

private AbstractTransformationMapping mapping;

@Override
public void initialize(AbstractTransformationMapping abstractTransformationMapping) {
    this.mapping = abstractTransformationMapping;
}

@Override
public Object buildFieldValue(Object o, String s, Session session) {
    if (o instanceof RouSub) {
        EntityManager em = MyEntityManagerFactory.getENTITY_MANAGER_FACTORY().createEntityManager();
        List results = em.createNamedQuery("Area.findByCode")
                .setParameter("area", ((Area) o).getCode())
                .getResultList();
        if (results.size() > 0)
            return ((Area)results.get(0)).getId();
    }
    return null;
}

}

    
risposta data 22.07.2017 - 21:24
fonte
0

So, I was thinking if I should have two Product classes. One for the service layer, the one that the customer will use when they call the API.

Bene, penso che nessuno sia in grado di dire cosa dovresti fare. Dipende più o meno dalle tue esigenze ma avere rappresentazioni diverse di solito è una buona idea.

Ad esempio, la rappresentazione fisica 1 e la logica 2 non sono necessariamente uguali. E la rappresentanza pubblica 3 potrebbe essere anche completamente diversa.

La ragione è semplice. Disaccoppiamento. Avere diverse rappresentazioni ci consente di modellare ogni livello con un certo grado di indipendenza rispetto agli altri, in modo tale che, se cambiamo la rappresentazione fisica, il cambiamento non influenzerà necessariamente quello pubblico.

Questo è particolarmente importante per le interfacce pubbliche (API) perché una volta in produzione, la modifica di interfacce e modelli ci porta a situazioni difficili. Il design delle API è particolarmente difficile per questo motivo. Dovremo sostenere il peso di tali decisioni nel bene e nel male.

Proprio ora, stai accoppiando strettamente la tua rappresentazione fisica con tutto il resto. Tieni presente che se cambi mai Product , anche i consumatori delle API ne subiranno le conseguenze. Al momento, non è possibile per te garantire la compatibilità con le versioni precedenti se Product cambia. Neanche il controllo delle versioni, a meno che non si mappa il doppio delle tabelle.

Se tutte queste cose sono importanti o meno dipende dalla tua situazione specifica, ma almeno merita una menzione.

1: Quello che memorizziamo

2: il modello dei dati di dominio

3: Il pubblico

    
risposta data 23.07.2017 - 12:17
fonte

Leggi altre domande sui tag