Pergunta

I'm just learning about clean architecture and I'm trying to design a proof of concept for an application I want to build soon.

In the Clean Architecture the Presentation layer and the Domain Model layer are separated by the Application layer. I understand how this make sense for stateless applications, like a Web base MVC application or a "record based" desktop application where most operations are CRUD. Also, my understanding is that the Presentation layer should not directly use the domain object, but should have it's own ViewModel that are mapped to/from models from the application layer. Is this assumption correct?

The application I plan to develop will allow the user to input chess games one move at a time. It seems to me that the rules for chess should be in the Domain Model layer. If this is the case, how does the Presentation layer validate each user input (to verify each moves legality)? Does it need to go through the Application Layer every time or does it makes more sense to let the Presentation Layer somehow manipulate the Domain objects directly to build the model according to the user input and then send it to the Application layer when it's time to save it to the database?

I tried to find resources online that would talk about this, but it seems all example/course/tutorial I found talk about a web application or at least a stateless application of the CRUD type where the business rules are applied once before saving the data or after loading them. In my application the chess rules needs to be applied every time the user edit the ViewModel to give an immediate feedback.

(As I wrote this, the rubber duck effect kicked in and I now think that maybe I should always go through the application layer. I would still like to know what more experienced people think)

Foi útil?

Solução

I am using Clean Architecture for a rich desktop application since about a year now and it works well for GUI-heavy, event-based applications. It's not a good fit for a "real-time" game like a jump'n'run, but a chess game will work just fine.

Instead of dissecting your question, I will go through a recap of the layers and will end with following the process of moving a figurine.

The Entities Layer

"Enterprise wide business rules". In your case, that means encapsulating the rules of the game. Having the age old game of chess here instead of an actual business gives you an advantage - once you wrote this layer, the only way it will ever need a change is if you found a bug here.

The Use Cases Layer

Think of use cases as application user actions. In this layer, you define these user actions, the input they take, the output they give back and the interfaces they use in order to achieve their goal. Such a use case will not really do any work itself. It's more like a step-by-step description of what happens.

The Interface Adapters Layer

The code you write that does the "grunt work" goes in here.

The Framework & Drivers Layer/Details Layer

This layer is purely an adapter from some external thing to your code (= the inner 3 layers). It consists almost completely of wrappers. This part needs to be so thin/simple that it doesn't need to know about the other 3 layers. It doesn't know about your models, it only works with strings, ints, events, etc.

The exception to this might be your setup code that connects everything, which is also in this layer for practical reasons rather than semantical.

(You called this the "Presentation layer", but that's a bit misleading. On one hand because a DB, some hardware device, internet access etc are also all external things that get their wrappers in this layer. On the other hand because all the "view logic" - like turning a model into strings for the GUI - happens in the previous layer.)

Moving a chess piece

Details layer

  • In the GUI, the user drags a chess piece to a different square
  • The class listening to that happening is a BoardView in the details layer
  • The BoardView calls a BoardViewPresenter with the simple info ('A', 1, 'B', 3)

Interface Adapters layer

  • The BoardViewPresenter translates that into proper models from your Entities layer.
  • It finds the ChessPiece on A1 and the BoardPosition B3.
  • It puts those into a MoveChessPieceRequest from the use case layer.
  • It sends that to the MoveChessPieceUseCase (obviously also from the use case layer).

Use Case layer

  • The MoveChessPieceUseCase validates the request according to the rules from the Entities layer.
  • It goes through the steps "The target square is free", "The type of chess piece can move like that" and "There's no check to resolve".
  • If any of the validation steps would have failed, the use case returns early a MoveChessPieceResult with a boolean Succeeded set to false.
  • However, in this example the request is valid, so it changes the Entities in memory and returns a positive result.

Interface Adapter Layer

  • The BoardViewPresenter now has the MoveChessPieceResult and reacts to the positive result.
  • It gets the ChessPieceView and tells it Move('B', 3).

Details layer

  • The ChessPieceView changes its actual position on the screen.
  • It also plays a "move" sound

Outras dicas

enter image description here

ViewModel Architecture and

enter image description here Clean Architecture are not the same.

In some ways they are incompatible. Please don't mix them carelessly.

The number one thing that separates them is cycles. ViewModel architecture is comfortable creating cycles in it's design. Clean (or Onion, or Hex) Architecture is not. ViewModel uses a binder to resolve the problems this can cause. Clean expects you to follow the Dependency Inversion Principle, make external layers swap-able plugins, and for you to do your binding yourself.

I'd go on but I've talked about this before. From here I'll just assume the ViewModel was a stray idea we can ignore.

In the Clean Architecture the Presentation layer and the Domain Model layer are separated by the Application layer. I understand how this make sense for stateless applications, like a Web base MVC application or a "record based" desktop application where most operations are CRUD. Also, my understanding is that the Presentation layer should not directly use the domain object, but should have it's own ViewModel that are mapped to/from models from the application layer. Is this assumption correct?

Many of these other terms aren't from Clean Architecture either. They are from Onion Architecture.

enter image description here

Here I mind the conflation much less because as Uncle Bob has admitted Clean Architecture explicitly lifts many of it's ideas and structure from Onion Architecture (which does the same from Hex / Ports and Adapters). It's all much the same idea with different buzz words that drive you to different blogs & books.

So as you were saying:

Presentation layer and the Domain Model layer are separated by the Application layer.

Yes, if by "domain" you mean Clean Architecture's Entities and by "Application layer" you mean Clean's Application Business Rules (Use Cases) and not OSIs Application layer.

Just be aware some people may map Onions domain onto both Cleans Entities and Use Cases. That doesn't mean separation logic isn't still a good idea. We're literally arguing semantics here.

So I'd say your assumption is correct. If I'm decoding your meaning correctly.

The application I plan to develop will allow the user to input chess games one move at a time. It seems to me that the rules for chess should be in the Domain Model layer.

I can agree with that.

... how does the Presentation layer validate each user input (to verify each moves legality)? Does it need to go through the Application Layer every time or does it makes more sense to let the Presentation Layer somehow manipulate the Domain objects directly to build the model according to the user input and then send it to the Application layer when it's time to save it to the database?

Validation often seems like it should happen in one place but this rarely works well. The reason is what's valid for one object isn't valid for another. Each object is meant to be an expert on its own thing. You could force it to happen in one place that must consider the whole application but consider this: The board knows how big it is. It doesn't know we're using it to play chess. Should the board assume chess will keep it safe or should the board say loudly "I'm only an 8 by 8 board. I don't know where to put this crazy 9,9 move you just made". Design the board like that and you can use its code to play checkers, tic tac toe, and go without teaching the board anything about those games.

I'd recommend letting the board validate against board issues and the game validate against game issues. And I'd consider both of these part of the "domain".

I tried to find resources online that would talk about this, but it seems all example/course/tutorial I found talk about a web application or at least a stateless application of the CRUD type where the business rules are applied once before saving the data or after loading them. In my application the chess rules needs to be applied every time the user edit the ViewModel to give an immediate feedback.

IO is slow. I might not be that good at speed chess but I still don't like the idea of sending moves directly to IO. Moves should go to memory first. But only after they've been validated.

One thing to understand here is that validation doesn't have to be enforced with exceptions. Every move is an attempt to change state. Not every move should result in a change of state. Every move should result in the user receiving feedback. So long as the validating object can provide that feedback in some way it's fine to shut down the attempt to change state.

And that brings me to output ports. One of the nifty things about Clean Architecture plugins is that despite everything being nicely a-cyclic, which keeps source code changes from propagating through your code base, you can still send the flow of control anywhere!

enter image description here

That works because of this little trick:

enter image description here

That open arrow head between Presenter and Use Case Output Port is asking for some kind of polymorphism. As long as you can provide that you can let info flow to anywhere you need it to and still ensure your architecture is a-cyclic.

(As I wrote this, the rubber duck effect kicked in and I now think that maybe I should always go through the application layer. I would still like to know what more experienced people think)

Sure, so long as that's the only place validators live. It's where I'd put them.

I'd tell you more about Clean Architecture and Chess but, well, I've also talked about that before.

In fact, I've talked about this a lot.

I think it's a great question and something I've been thinking through as well. The approach I've been taking is:

  • First address the domain objects. In the chess world, you'll probably have a Board and a set of Piece objects. The Piece object has concrete classes (e.g. Rook, Queen, Pawn, etc) that inherit from a generic piece (e.g. all pieces can move on a board, but are unique to the piece). Build the unit tests to validate the domain logic.
  • Next, build the application layer. The application will manipulate the board state and ask the domain whether moves are legal. Again, write unit tests to validate the logic around the chess application
  • Finally, build the UI on top of the app layer. The UI should only need to send to the app "move the piece on E5 to E7". The app would then say things like "move successful" or "move invalid"

Keep in mind that Clean Architecture, like many other architectural patterns for building applications is just a means of codifying what logic goes where. Each one of them have specific goals and challenges they are trying to address.

There is nothing in the write-up on clean architecture that requires you to only work with the layer just below yours. You can interact with any layer lower than the one you are working on. So what does that mean?

  • You can work with the application layer right from the UI (like you surmised in your rubber duck session)
  • You may not need all the things in each layer for your particular app, but if you did you know where to put it.

Similar to how a seamstress can grow into being a fashion designer, you will find that those patterns don't have to be used completely as written. You will find that you can do a little mix and match between a couple of different patterns to come up with something that works better for your vision. Those patterns are always there for inspiration and to provide a basic structure to work from.

Licenciado em: CC-BY-SA com atribuição
scroll top