I see you are trying to follow Uncle Bob architecture. So I could share some personal experiences since I did most of the things you did in that project (all of them actually).
Also, worth to mention that this is an art and there are many ways, there is no right way to do it.
I will be using this package as an example, https://github.com/straw-hat-team/straw_hat_map
After doing what you did, I decided to move to DDD folder structure. The issue that your folder structure has, in my opinion, is that represents a programmer driven structure.
Entities
Originally I had it the way you have it right now, a mega folder (it will get big) with most of my schemas (entities), most of the time it was a pain having to go inside another folder all the time when the schema is only used on the a specific context (most of the time this will be the case if you do the DDD right).
What would happen when you have two schemas that would make sense to have the same name, but they are different depending on the context?
For example (for the sake of making a point), account
from Accounts context
and account
from Sales context
.
How will you structure your schemas
folder now? You will keep creating folders or files (it will get big)
I am trying to make a more DDD approach to my folder structure. I no longer have any folder called schemas
/entities/
models,`.
I put a file in singular module and file name closer to the context that will be used, so you don’t have to jump between many folders.
Reduce the entropy of the files and folders.
Example: https://github.com/straw-hat-team/straw_hat_map/tree/master/lib/straw_hat_map/addresses
Queries
I would say, wait until you need actually to have a file with all the queries.
Initially, once again, I did what you did, but I had to go inside queries
folder or file and to have to go back and forward between files that most likely will be touch at the same when you are working in that specific context.
I removed the concept of having queries inside a folder/file.
I put the queries on my context module and unless the file becomes problematic to manage I will no move it.
Most of those functions will become simply private functions on your context module; but once again, one less file that you need to open and paid attention to (wait until you have an issue).
Reduce the entropy and file management.
Worth to mention that most of those queries will be import in use cases that belong to the same context, so you will have to keep importing the same query module for all the services from a specific context.
Services
Once again since I did the same, I removed this concept of service.
My service is my context module, or I would call the module that implements the use cases https://github.com/straw-hat-team/straw_hat_map/blob/2baa9fc69e7d69b51239a86180d694df5b202a16/mix.exs#L100-L108 you see that those modules will perform the business use cases.
I don’t need to open many files to implement or work on use cases. Especially that becomes more problematic as the use cases start sharing pieces of code.
With your solution, the issue starts with the fact that all your modules will have a process
function or some function that it will implement the use case because your module name is the name of the use case.
In my case, the functions are the name of the use case, and I don’t need to create that many files.
Example: https://github.com/straw-hat-team/straw_hat_map/blob/master/lib/straw_hat_map/world/countries/countries.ex
All the public functions (or at least that will appear on ex_doc
documentation are most likely use cases).
The ugly comes when you need multiple use cases in one use cases.
Since you went for the route of using the module name as the use case namespacing, now you will need to import modules when you need to have access to the use case.
Even when you are in the same context,
and if you decide to create some function that could is shared between use cases, the mess start becoming even more prominent.
In the approach I ended up with, the use cases for specific context are closer to each other, and it is easier to reuse.
Reduce the entropy between your shared functions.
Module Names vs Folder Structure
Worth to mention that the name of my modules does not follow the folder structure.
The source code is optimized for readability and usability than actually finding files by module names.
You can always do a generic search for function module names, at least you don’t have long names on your modules just because you want to follow the folder structure.
Also module name driven by folder structure makes is harder to move files around since you will have to rename everything.
I tend to create a folder for creating some context and encapsulate files that would most likely live together.
TD; TR
Reduce the entropy of your files and code, the higher the entropy the harder becomes to maintain in, the long term (I love to open Erlang source code because of this)
Use your module as the namespace for your context and use functions as your use cases.
Do not use folder structure as your module name.
Separate files and module based on contexts, no responsibilities.
Clean code is not about conventions.
Conclusion
I hope you find my comment useful.
As I said, I did all the things you propose here and to be honest, I hated myself doing it.
Because of my background from Rails and the habit of convention I had to expirience the pain of learning about this.
I am pleased with the Straw Hat repositories structure, it is nice for consumers of the packages and maintaining it is easier since you don’t need to have that many files open, and the entropy of the files are minimal.
I would say that the only downside is that the folder structure does not represent the module names so you can’t simply assume where the module name locates the file, but you can always search for it, the trade-off.
Still learning and improving, just keep DDD and entropy in mind.
But, no there is nothing wrong with your setup, but subjective speaking I wouldn’t do it since I did exactly what you did and I moved away from it.