I am wondering about this. This is a sort of follow-up to my last question here:
because now I've run into the question of how to deal with multi-threading in this application. For readers who have not read that, the application is a desktop application and built using an MVC-style pattern. The problem that I have is that, initially, I was starting out with it as single-threaded, but it seems that the particular GUI framework that I'm using wants to do things in a multi-threaded manner with one thread per each window displayed. Now this makes some sense, of course, as it lets those windows not interfere with and block each other. However, the problem is that all that UI stuff has to communicate with that common model layer, and it does so through an interface comprised of "services" - see "tereško"'s post on Stack Overflow here:
Basically, the view and controller parts get injected with services through a dependency injection system, and they can then call functions on those to cause changes in the model layer like modifying an open document, for example, or opening new documents, or calls that would get forwarded down to a deeper "persistence layer" such as saving and loading a document from disk storage. The problem, then, is what if something in the model layer implementation creates a conflict when two service functions are called in different UI threads? Moreover, ideally the model layer should not have to change to meet threading needs of a particular UI package, and it should be possible to switch mono and multi-threaded UI packages out with ideally no changes to it. And this all isn't getting to the possibility that the model layer, itself, may spawn work threads.
And the question is, how do you "neatly" design the interface To remedy this, I've come up with the following possible ideas:
- Just try to "wing it" - try to be careful about what you call on the model layer where. However, this feels like it will inevitably leak knowledge of the model layer internals to the UI layer.
- Make the model layer code "thread safe". The way the program is now, there is already a system of handles that identify which data the user interface wants to mutate, e.g. which open document. So one could simply take care to structure the model layer services so that nothing else gets mutated except what the caller asks for by handle - that is, if it makes two mutator calls with different handles, it can be assured that there will never be a simultaneous access of the same data anywhere in the model layer and it can do whatever it wants insofar as threads are concerned. Basically, each mutator should mutate a well-defined segment of the model, and the UI can rely on that there will be no crossing of those boundaries. However, enforcement of this "feels like" leaking UI knowledge down into the model layer (the opposite of 1), because it wouldn't necessarily get done in the absence of this multi-thread UI,
- Disconnect the two with some kind of separate "glue" interface. The model layer runs and has control over its own threads. All service methods are async: they accept a callback to get a result, instead of a return value. Likewise, the UI layer controls its threads. This would seem the neatest approach, but means redesigning the interface of the service layer. Again we end up leaking knowledge that threading is a concern "somewhere", at least.
What do you suggest? Is it best to just "eat it" and accept that there must be a leakage? I've seen a number of posts talking about how that this type of thing is in general not something that lends itself ideally to abstraction. What I am just interested in is understanding how that "good design" principles here like loose coupling apply or do not apply in this situation. I could obviously implement any one of the above options, but I want to know what "good practice" is.