Confronto tra approcci di mappatura di oggetti dati

1

Aggiornamento: ho aggiunto alcuni diagrammi per aiutare a capire

Ho avuto una discussione con un collega su due diversi approcci nella mappatura degli oggetti dati. Mi piacerebbe avere la tua opinione su pro e contro tra i due approcci.

Alcune informazioni di base. Abbiamo due servizi esterni. Uno è raggiunto su SOAP, l'altro su REST. Restituiscono oggetti di natura simile (punti di consegna postali). Poiché sono diversi servizi esterni, gli oggetti dati sono diversi. Ad esempio (semplificando eccessivamente gli oggetti dati reali):

public partial class ParcelShopA
{
    public string City { get; set; }
    public string Address1 { get; set; }
} 

e il servizio 2 potrebbe essere:

public partial class ParcelShopB
{
    public AddressB Address { get; set; }
}

public partial class AddressB
{
    public string Street { get; set; }
    public string HouseNumber { get; set; }
    public string City { get; set; }
}

Ora, l'obiettivo è combinare le risposte dei due servizi in un unico formato unificato ed esporlo. Quindi abbiamo la nostra classe:

public class UnifiedAddress
{
    public string Address { get; set; }
    public string City { get; set; }
}

Ecco un diagramma che mostra il problema iniziale, prima di applicare qualsiasi soluzione:

Approccio1:lamiaideaeradicreareunmapperseparatopercaso.Nellamiamente,èsemplicecasodiprogrammazionefunzionale,incuivogliamopassaredall'oggettoAall'oggettoB.

publicclassConverterA{publicUnifiedAddressConvertToUnified(ParcelShopAp){//TODOimplement}}

elostessoperB:

publicclassConverterB{publicUnifiedAddressConvertToUnified(ParcelShopBp){//TODOimplement}}

Ora,tienipresentecheogniservizioverràimplementatoall'internodiunprogettodicodiceseparato(probabilmenteancheunmicroservizioseparato),quindituttalalogicasaràcontenutalì.

Eccocomesarebbelasoluzione:

Approccio2:l'approccioalternativoerasimilealseguente:

  • Definisciun'interfacciacomeIParcelShopchedefinisceirequisitipertuttiinegozidipacchi
  • DefinisciunaclassecheconvertedaIParcelShopall'indirizzounificato
  • DatochesiamoinC#eabbiamoclassiparziali,implementaIParcelShopinognioggettodati.

Quindiavremoqualcosacome:

publicinterfaceIParcelShop{stringGetAddress();stringGetCity();}

eilmapperconvertedaIParcelShop:

publicclassMapper{publicUnifiedAddressConvert(IParcelShopparcelShop){returnnewUnifiedAddress{City=parcelShop.GetCity(),Address=parcelShop.GetAddress()}}}

Ora,datocheabbiamoclassiparziali,possiamoaggiungerequestafunzionalitàaglioggettidatideiservizi,ades.perilprimopaccosarebbe:

partialclassParcelShopA:IParcelShop{publicstringGetAddress()=>Address1;publicstringGetCity()=>City;}

eperilsecondoforse:

partialclassParcelShopB:IParcelShop{publicstringGetAddress()=>Address.Street+" " + Address.HouseNumber;
    public string GetCity() => Address.City;
}

Ecco un diagramma che mostra la seconda soluzione:

Nella mia mente il primo approccio è molto più pulito perché non tocca gli oggetti dati, non si basa su classi parziali e contiene la logica di mappatura in un unico posto.

Quali argomenti presenteresti a qualcuno per convincerlo a favore dell'una o dell'altra soluzione?

    
posta Nikolaos Georgiou 02.03.2018 - 16:24
fonte

2 risposte

2

Vorrei andare con un modello unificato che contenga tutte le informazioni richieste riguardanti gli indirizzi postali dai diversi servizi esterni. Questo sarà usato nelle "internals" della tua applicazione.

I tuoi clienti per i servizi esterni con le interfacce richieste includeranno un livello di traduzione dalla rappresentazione interna allo schema richiesto dal servizio e viceversa. IMO concettualmente questo è il loro posto in quanto mi aspetto che ogni servizio esterno richieda una traduzione diversa.

In questo modo hai una separazione / astrazione della tua applicazione principale che si occupa di un modello unificato e dell'interazione con le parti esterne. Le modifiche al servizio esterno non influiranno sul resto dell'applicazione, ma solo sul client. È possibile aggiungere / rimuovere client da servizi esterni più facilmente.

    
risposta data 02.03.2018 - 18:27
fonte
0

Penso di poter provare a rispondere alla mia stessa domanda.

Innanzitutto, disegnare i diagrammi era molto importante , che è una lezione a parte. È più facile discutere su qualcosa di visibile, almeno per me. Forse questa è la lezione più importante per me.

La prima soluzione implementa la logica di mappatura all'interno del microservizio utilizzato. Non modifica alcuna altra classe e non aggiunge logica ai DTO. Poiché i servizi esterni sono diversi, hanno DTO diversi, quindi la logica di mappatura sarà diversa per definizione.

Quindi, i problemi con la seconda soluzione. In sostanza, introduce un livello extra di mappatura, che non aggiunge alcun valore. Il Mapper ha ben poco da fare, dato che IParcelShop è già modellato per essere molto vicino alla classe finale desiderata ( UnifiedAddress ). La logica di mappatura specializzata è ancora all'interno dei microservizi, ma questa volta si associa a un'interfaccia creata.

L'altro problema è che questa soluzione modifica oggetti dati come ParcelShopA . Prima di tutto, l'unica ragione è possibile perché il linguaggio di programmazione lo supporta (classi parziali C #). Nella mia mente si tratta di una violazione sia del Principio della singola responsabilità che del Principe aperto / chiuso.

Violare SRP perché ParcelShopA ha uno scopo e deve essere un DTO per un servizio esterno. All'improvviso, sa anche come mappare su un tipo di dati diverso. Cosa succede se vogliamo mappare anche qualcos'altro? Ci aggiungeremo di più?

Inoltre viola il principio Open / Closed. A quanto ho capito, aggiungere codice a una classe significa modificarlo, non estenderlo. Qui, a causa della classe parziale, si potrebbe dire che questa è un'estensione e non una modifica. Ma penso che tu lo stia chiaramente modificando; il fatto che le modifiche possano vivere su un file .cs separato non cambia questo fatto.

Il problema generale è che la soluzione sta cercando di applicare una soluzione OOP a qualcosa che non è stato risolto da OOP. Trasformare da una classe all'altra è una mappatura, una pura funzione. La programmazione funzionale è più adatta qui.

Considerando i microservizi, un problema che entrambe le soluzioni hanno è l'esistenza di un progetto condiviso. Il progetto DTO condiviso esiste solo per rafforzare la coerenza degli schemi tra i tre microservizi. È conveniente perché garantisce che tutti e tre i progetti utilizzino lo stesso contratto nei messaggi scambiati. Tuttavia, la seconda soluzione peggiora il problema aggiungendo più logica a quel progetto. Da quanto ho capito, copiare la classe UnifiedAddress nei 3 progetti è qualcosa che le persone sostengono in un'architettura di microservizio. Mi piace questo articolo su questo argomento: Condivisione di codice tra microservizi?

    
risposta data 04.03.2018 - 07:52
fonte