Does my code/directory structure makes any sense?

Hi all, I’m relative new to Elixir/Phoenix here.

I’m trying to structure my code properly for ease of development. I’m also trying avoid bloated contexts, applying SRP to the best of my ability. Would really appreciate if I can get some feedback on my github repo here . Am I doing too much, too little or does it not make sense at all? Thanks in advance!

2 Likes

Yes it does! Since most of the time you’ll be sharing schemas across contexts its a good place you put them. Just remove the _ from the folders, so they all can conform on a notation :slight_smile:

1 Like

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.

8 Likes

Thanks all! I’m glad I’ve asked. I’ve learnt so much from the responses and I’ll be sure to checkout the example projects.

I need to write another answer here. So much has changed for me that I wouldn’t suggest doing that, except if you don’t have any better strategy. :sweat_smile:

1 Like

Please do!

At the moment I’m mostly using the basic gen context structure, but started modifying it so that sub-modules relating to a domain sit in a nested folder as my logic is growing. So the main context handles the aggregate use cases and makes calls to sub-domain use cases that handle their own models. If I don’t get a grip on a better system soon it’s going to get difficult once I start bringing more devs into the project.