2

I have been implementing a proof of concept application using Uncle Bob's Clean Architecture and I have run into a bit of a problem.

Uncle Bob's architecture calls for the explicit separation of request and responses using interfaces. This is not a problem in most cases (e.g. when implementing a UI using the MVP pattern) but I don't know how to apply this to create a REST API using Spring MVC.

My Controller has a method with the following signature:

Response<String> greet(String name)

mapped to /greeting that takes a name and outputs a different greeting depending on the value of the name.

Injected into the Controller is the UseCase that receives the name and creates the greeting, sending the output through the OutputPort injected into it.

The problem is that I cannot separate the inputs and outputs in this way because the Controller needs to interact with both the inputs and outputs to create a response.

The only way to "implement" this, that I can come up with, is returning the value using the InputPort, which sounds pretty bad and not at all what a Clean Architecture calls for.

I've been thinking about this and I cannot find any way that my Controller can act both as a Controller and as a Presenter at the same time. Am I missing something here? Is there a better desing that would allow the separation of the inputs and outputs of the REST API without massively overcomplicating things?

EDIT: I have been reading again chapters 23 and 24 of Clean Architecture and I sure could have phrased my questions a lot better. What I have right now as a (working, perfectly fine) solution is a One-Dimensional Boundary (page 219) and I was wondering if I could extend this and separate this interface into two reciprocal interfaces. Hopefully this clarifies my point a bit.

  • Does your controller do much more than passing calls to your underlying "businesses" model or service? – Adriano Repetti Jul 1 '18 at 8:44
  • @AdrianoRepetti No, it shouldn't have any logic other than, maybe, convert the response object to a REST friendly format (JSON or the like). – Carlos Jul 1 '18 at 8:49
  • 1
    Then IMHO forgot about "clean" under-explained, opinionated, unnecessary, redundant "architectures" and you already have a clean separation between your layers. Your controller here is your interface adapter (if you want to call it this way) and that's all you ever need. Anyway, even with my hostility against "clean" buzzword I think you're following its principles. – Adriano Repetti Jul 1 '18 at 9:03
  • @AdrianoRepetti Yes, this is something of a minor problem, because the separation is clearly there. Although, that being said, it was still rubbing me the wrong way having to change the interfaces to add a return value to an input port. – Carlos Jul 1 '18 at 9:33
  • @Carlos, I strongly suspect that you are misunderstanding, or misapplying, the architectural constraints that Uncle Bob is recommending. But it's difficult to untangle without reference the specific guidance that you think applies. You might improve the question with quotations or links to the guidance that is giving you trouble. – VoiceOfUnreason Jul 1 '18 at 11:51
1

OK, I think I see what's going on: you've got a context impedance mismatch.

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

The basic idea to recognize is this: the "View", so to speak, changes with every HTTP request that arrives (more precisely, it has a scope coupled to the lifetime of the HTTP Connection, but that's not important here).

That means that we need to compose a new Controller->Use-Case-Interactor->Presenter pipeline for each request.

The good news: Spring is already doing that for you under the covers.

The bad news: Spring is using a single Input-Output boundary, rather than separating them.

I say "bad news" because you are trying to make this round peg fit into the square hole described by Martin. I think what Spring is doing here is "fine", it's just inconvenient for the interfaces you want.

So let's pretend: you have a use-case-interactor that expects to be wired to an input boundary and an output boundary, and you have this signature on your controller. Now what?

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));
}

If you were a bit luckier with the API for your UseCaseInteractor, it might instead look like:

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));
}

You might find this more familiar if we were to structure it as a 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));
}

The core idea, though, is that you have two functions; in the module with your use case interactor, you have a function that looks like

InputData -> OutputData

and in your web layer, you have a function that looks like

OutputData -> ViewModel

the input and output boundaries are just a way to compose those two functions without introducing a dependency that points the wrong way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.