Implementazione di un'API REST in un'architettura pulita

2

Ho implementato un'applicazione proof of concept utilizzando l'architettura pulita di Uncle Bob e ho avuto un piccolo problema.

L'architettura di Uncle Bob richiede l'esplicita separazione delle richieste e delle risposte usando le interfacce. Questo non è un problema nella maggior parte dei casi (ad es. Quando si implementa un'interfaccia utente utilizzando il pattern MVP), ma non so come applicarlo per creare un'API REST utilizzando Spring MVC.

Il mio Controller ha un metodo con la seguente firma:

Response<String> greet(String name)

mappato su /greeting che prende un nome e genera un saluto diverso a seconda del valore del nome.

Inserito in Controller è il UseCase che riceve il nome e crea il saluto, inviando l'output attraverso OutputPort iniettato in esso.

Il problema è che non posso separare gli input e gli output in questo modo perché Controller deve interagire con gli input e gli output per creare una risposta.

L'unico modo per "implementare" questo, che posso inventare, è restituire il valore usando InputPort , che suona piuttosto male e non è affatto quello che richiede un'architettura pulita.

Ci stavo pensando e non riesco a trovare alcun modo in cui il mio Controller possa agire sia come Controller sia come Presenter allo stesso tempo. Mi sto perdendo qualcosa qui? Esiste un desing migliore che consentirebbe la separazione degli input e output dell'API REST senza complicare eccessivamente le cose?

EDIT: Ho letto di nuovo i capitoli 23 e 24 di Clean Architecture e sicuramente avrei potuto formulare le mie domande molto meglio. Quello che ho adesso come soluzione (funzionante, perfettamente funzionante) è un limite unidimensionale (pagina 219) e mi chiedevo se potevo estendere questo aspetto e separare questa interfaccia in due interfacce reciproche. Spero che questo chiarisca un po 'il mio punto.

    
posta Carlos 01.07.2018 - 10:12
fonte

1 risposta

1

OK, penso di vedere cosa sta succedendo: hai una mancata corrispondenza dell'impedenza di contesto.

You're in a framework now, Mr. Bond. The game with the composition is played a little bit differently here.

L'idea di base da riconoscere è questa: la "Vista", per così dire, cambia con ogni richiesta HTTP che arriva (più precisamente, ha un ambito accoppiato alla durata della Connessione HTTP , ma non è importante qui).

Ciò significa che dobbiamo comporre una nuova pipeline Controller->Use-Case-Interactor->Presenter per ogni richiesta.

La buona notizia: la primavera lo sta già facendo per te sotto le coperte.

Le cattive notizie: Spring utilizza un singolo limite Input-Output, piuttosto che separarli.

Dico "cattive notizie" perché stai cercando di far rientrare questo piolo rotondo nel buco quadrato descritto da Martin. Penso che quello che Spring sta facendo qui sia "fine", è solo scomodo per le interfacce che vuoi.

Quindi facciamo finta: si ha un interfase del caso d'uso che si aspetta di essere collegato a un confine di input e un limite di output, e si dispone di questa firma sul controller. E adesso?

Response<String> greet(String name) {
    InputBoundary in = inputBoundaryFor(name);

    // Arbitrary choice
    List<String> greetings = new ArrayList();
    OutputBoundary out = outputBoundaryFor(greetings);

    // Here's our composition with request scope.
    UseCaseInteractor uci = useCaseInteractorFor(in, out);

    uci.run();

    String greeting = greetings.get(0);

    return responseFrom(greetings.get(0));
}

Se tu fossi un po 'più fortunato con l'API per il tuo UseCaseInteractor, potrebbe invece assomigliare a:

final UseCaseInteractor uci = ...

Response<String> greet(String name) {
    InputBoundary in = inputBoundaryFor(name);

    // Arbitrary choice - any reasonable container for
    // for a result could be used here.
    CompleteableFuture<String> greetings = new ArrayList();
    OutputBoundary out = outputBoundaryFor(greetings);

    uci.run(in, out);

    String greeting = greetings.get();

    return responseFrom(greetings.get(0));
}

Potresti trovarlo più familiare se dovessimo strutturarlo come callback

Response<String> greet(String name) {
    // Arbitrary choice - any reasonable container for
    // for a result could be used here.
    CompleteableFuture<String> greetings = new ArrayList();

    uci.run(name, (greeting) -> {
        greetings.complete(greeting);
    });

    String greeting = greetings.get();

    return responseFrom(greetings.get(0));
}

L'idea principale, però, è che hai due funzioni; nel modulo con il tuo interlocutore caso d'uso, hai una funzione simile a

InputData -> OutputData

e nel tuo livello web, hai una funzione simile a

OutputData -> ViewModel

i limiti di input e output sono solo un modo a per comporre queste due funzioni senza introdurre una dipendenza che punti nel modo sbagliato .

    
risposta data 03.07.2018 - 16:23
fonte

Leggi altre domande sui tag