Come si astraggono i valori di richiesta / risposta tra interfaccia utente / presentazione?

0

Ho dato la seguente architettura:

Livelli:

Bootstrapper

MyApp.Start

  • Contenitore-IoC ecc.

Livello interfaccia utente

MyApp.Gui

  • Vista
  • ViewModels

Livello aziendale

MyApp.Core

  • DataGenerator
  • DataGeneratorRequest
  • DataGeneratorResponse

MyApp.Core.Interfaces

  • IDataGenerator
  • IDataGeneratorRequest
  • IDataGeneratorResponse

Esiste un metodo Generate all'interno di IDataGenerator che accetta IDataGeneratorRequest come parametro e restituisce IDataGeneratorResponse.

Problema:

Al momento il livello del contenitore IoC può alimentare la mia interfaccia utente con un IDataGenerator, ma dovrei comunque fare riferimento al Core-Project per creare una richiesta e lanciarla nel metodo Generate ogni volta che è necessario. Non mi piace e vorrei liberarmi della dipendenza.

In un mondo perfetto, come sarebbe architettato?

  1. È stato utile creare interfacce per le mie richieste / risposte in primo luogo?
  2. Le classi request e response appartengono invece al progetto Core.Interface?
  3. O dovrei aggiungere un terzo progetto, che ha tutte le classi in esso, che vengono scambiate?
posta Jannik 02.07.2018 - 14:18
fonte

1 risposta

1

Was it a good thing to create interfaces for my requests/Responses in the first place?

È difficile da dire. Dipende davvero da quanto sono complessi gli oggetti richiesta / risposta, o se richiedono alcuni aggancio interni all'implementazione di IDataGenerator . Offre la massima flessibilità.

Do the request- and response-classes belong into the Core.Interface-project instead?

Dipende dalla risposta alla prima domanda. Se hanno ganci interni al tuo DataGenerator , allora no. Se sono oggetti semplici completamente autonomi, quella sarebbe l'opzione più semplice.

Or should I add a third project, which has all classes in it, that are exchanged?

Probabilmente no.

Hai alcune opzioni e ciò che scegli in realtà dipende dalla complessità degli oggetti di richiesta / risposta. La prima cosa che devi decidere per il tuo progetto MyApp.Core.Interfaces è se vuoi essere severo su tutto ciò che è un C # interface , o se puoi consentire semplici oggetti per la tua richiesta / risposta . La linea di fondo è che i consumatori del tuo IDataGenerator devono essere in grado di creare un'istanza dell'oggetto richiesta.

Opzione 1: Mantieni tutte le interfacce letterali

Per fare ciò, devi aggiungere un meccanismo per creare la tua richiesta. Il modo più semplice per farlo è passare un lambda o fornire un metodo IDataGenerator.CreateRequest() . Le nuove API Net Core tendono a utilizzare l'approccio lambda:

IDataGeneratorResponse Generate(Action<IDataGeneratorRequest> getRequest);

Sarebbe chiamato qualcosa del genere:

var response = dataGenerator.Generate(request => {
    request.DataType = DataType.MockReport;
    request.From = DateTime.Now.AddDays(-7);
    request.To = DateTime.Now;
});

Il lato dell'implementazione sarebbe simile a questo:

public IDataGeneratorResponse Generate(Action<IDataGeneratorRequest> getRequest)
{
    // NOTE: assumption is that DataGeneratorRequest is a class and
    // not a struct

    var request = new DataGeneratorRequest();
    getRequest(request);

    // ... do all the data generation

    return new DataGeneratorResponse(calculatedData);
}

Naturalmente, puoi essere libero di renderlo più di un'API fluente, se lo desideri. Entrambi gli approcci funzioneranno.

Opzione 2: Mantieni logicamente le interfacce

In questo caso avresti una classe DataGeneratorRequest completamente sviluppata, oppure avresti una factory per crearli. Comprendo che potresti non essere in grado di eseguire l'implementazione completa della classe DataGeneratorRequest nel tuo progetto MyApp.Core.Interfaces , motivo per cui in tal caso ti occorrerà una fabbrica.

L'approccio ibrido consisterebbe nel rendere la classe base per DataGeneratorRequest un costruttore protetto e un metodo di produzione pubblico come questo:

public abstract class DataGeneratorRequest : IDataGeneratorRequest
{
    protected DataGeneratorRequest() {};

    // .... the rest of the implementation

    public static IDataGneratorRequest Create()
    {
        // You'll have to use reflection here to create the instance
        // and a way for your full implementation to inform the factory
        // what to instantiate.
    }
}

Opzione 3: riferimento ad entrambe le interfacce e implementazione

L'ultima opzione è fare affidamento sul fatto che si avrà l'implementazione del IDataGenerator a cui si fa riferimento nel progetto finale e nelle interfacce. La tua implementazione di DataGeneratorRequest è completamente sviluppata nell'assembly di implementazione e puoi semplicemente crearla come qualsiasi altro oggetto:

var request = new DataGeneratorRequest
{
    DataType = DataType.MockReport,
    From = DateTime.Now.AddDays(-7),
    To = DateTime.Now
};

var response = dataGenerator.Generate(request);

Questo è il più semplice da implementare, ma non sarai in grado di costruire cose per consumare il tuo generatore di dati senza fare riferimento all'assembly di implementazione. Potrebbe non essere il modo in cui desideri organizzare la tua applicazione, in particolare se vuoi separare i consumatori della tua interfaccia e caricarli in modo dinamico.

Compromessi

  • L'opzione 1 garantirà che i consumatori avranno solo bisogno dell'assemblaggio MyApp.Core.Interfaces per eseguire il proprio lavoro. Si assicurerà inoltre che siano progettati per consumare la stessa versione dell'oggetto IDataGeneratorRequest . C'è una quantità gestibile di complessità e nessuna riflessione.
  • L'opzione 2 imporrà la tua implementazione DataGeneratorRequest in modo molto semplice, o avrai bisogno di una riflessione. In entrambi i casi, l'API è ancora molto utilizzabile e richiederà solo un riferimento all'assembly MyApp.Core.Inferfaces .
  • L'opzione 3 è la più semplice da implementare, ma aggiunge complessità agli utenti dell'API. Ciò è principalmente dovuto al fatto che l'API non è completamente specificata nell'assembly MyApp.Core.Interfaces e richiede un altro assembly a cui fare riferimento. Perdi la flessibilità, ma potresti avere errori impercettibili se hai diversi assembly per i consumatori che sono stati creati con versioni leggermente diverse dell'assembly di implementazione.

Per essere onesti, sono più parziale della prima opzione. Sembra più naturale il modo in cui le app Net Core sono configurate e configurate in questi giorni.

    
risposta data 02.07.2018 - 15:30
fonte

Leggi altre domande sui tag