4

All the examples across the internet try to pretend that every application is run on a single thread. There is no problem with synchronization, multithreading etc. Uncle Bob, in his "Clean Architecture" book mentions the topic but very briefly and suggests to postpone some decision as long as possible.

But real applications do not look like that. Even a very simple application (like "BuckPal" from "Get your hands dirty on Clean Architecture) should take it into account.

Let's say we have a bank Account entity. In a use case we get an Account object, withdraw some money and then save the account. And we are happy. No comment about synchronization, locking etc. In typical CRUD and DB framework centric application we usually use database transaction to synchronize access.

I don't see where all the stuff related to multithreading and synchronization should be put (there is a chance those are two different topics but I don't know).

4

Generally the best place to put all the multithreading stuff is "as far away from you as possible".

That's a joke, but also a very serious point - it's hard to get right, so most architects will try to avoid intricate or fine-grained multithreading. Web applications, as you say, tend to handle one request with one thread and let the underlying database handle data concurrency. For parallelizing work in that context it's often more reliable to outsource it to other services (microservices), which share no memory.

Hence also the popularity of immutable data structures, which can be safely shared among multiple threads without conflicting modifications.

4
  • Yes, that the problem with examples, especially stateless (excluding DB) application examples. But I want to create a game house - application that needs to keep state and that does not use any external mechanism to synchronize access. Everything is in memory. Does it mean that my application and domain layer are going to be infected with threads, locks etc.? Dec 17 '20 at 15:36
  • Game design is specific enough and has its own techniques that it might be worth a separate question: how to handle multithreading in game architectures. May already exist as a question. May be worth considering whether your game is doing enough to require threads at all!
    – pjc50
    Dec 17 '20 at 16:18
  • Yes, you are right. But my focus is on understanding Clean Architecture. So what I'm doing now probably is an over engineering:) Anyway: If you have some more specific hits regarding this topic then they are more that welcome:) I think that when say "game" its not clear what kind of application I'm thinking of. I think that best approximation is a poker server where you have players, rooms and games. Dec 17 '20 at 16:46
  • @user2146414 I don't know how this fits into Clean Architecture (didn't research it) but - if your game server handles several games at once and each game has a few players (anything from tic-tac-toe to Age of Empires) you would try to have one thread per game. If you only have one game with tons of players (World of Warcraft), that's harder - you could try to split the game world up into regions and have one thread per region and design the game so stuff that happens in one region doesn't affect another.
    – user253751
    Dec 17 '20 at 18:33
4
+50

First, Hexagonal/Clean Architecture is not over-engineering, it is just bad engineering for most, if not all contexts. Here is an article of mine explaining in detail what requirements Clean Architecture would imply and why that's rarely applicable to real projects, and not applicable at all to object-oriented ones.

So what about multithreading? In practice there are two things that you have to do:

  1. Make everything non-blocking. This is the hard part. In Java that's achieved with CompletableFuture, in Kotlin it's suspend functions, every language has some construct for this. It takes time to understand these things, take this time, it is very important. Unfortunately this is not transparent (in most languages) and may influence API designs.
  2. Once things are non-blocking, multithreading can be done at any point where you feel you need it, since all the constructs above already support it.

So for example to close an account you would have:

public interface Account {
   void close();
}

That is blocking, since it waits for the I/O with backend and/or database. So instead of that you have to do:

public interface Account {
   CompletableFuture<Void> close();
}

That is non-blocking (if correctly implemented), you can choose to wait on the future, but you don't have to. In any case, it will not consume a thread for the duration of the wait.

So if you want to close a 100 account simultaneously, you can write (pseudocode):

allOf(                          // Bundles all following futures
   accounts.map(Account::close) // Calls close on all accounts
).thenAccept(nothing ->         // Called when everything is complete
   log("closed all accounts");
);

This will not use 100 threads to do this, but will use the thread-pool of the backend connection "automatically".

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.