Project structure and layering

Yeah, I keep such validation directly in context. If the function is small, the checks are inlined, otherwise they are extracted into some private fun.

What’s wrong with calling one context from another?

As an example the People context (contains User) has many other contexts depending upon it. I’m trying to avoid that context calling out to, or even knowing about, other contexts which depend upon it. There are schemas in other contexts that have a belongs_to association to User, but the User schema does not have a has_many to these resources.

Suppose I am archiving a user. There can be resources from various contexts that need to be removed or disabled and other shutdown operations, reporting, etc. This means calling out to contexts like Intercoms, Printers, Networking, etc. Currently that is happening in a module dedicated to that task, ArchiveUser. It mostly consists of a call function that contains a Multi that walks through the steps. These “cross-context” modules also sometimes have their own dedicated embedded schemas and changesets for custom workflow forms.

In some ways, these are just another context that happens to reach across multiple contexts, but they are smaller and typically only focused on one main operation so I dumped them into that “/services” directory. It was also just familiar to turn these into verb “use cases / services” because I did that quite a bit back in my Rails days.

Avoiding calling child contexts from a parent context is the only scenario where I’ve tried to be vigilant. Other than that, I’ve generally tried to limit the amount of dependencies between smaller contexts out on branches of the dependency tree. If I find that two otherwise independent contexts seem to need to be dependent upon each other, I take that as a sign that maybe they belong in the same context. This just happened recently where I merged the CRM and Leasing contexts into one.

I am also interested in trying your Boundary library to further enforce the dependency tree amongst contexts, but wonder if that would be a misuse of the intent of the library.

2 Likes

Oh I see. Yeah that’s fine. I asked b/c I occasionally see people advising against calling one context from another, which is for me too dogmatic. But if I get this right, you otherwise call contexts from other contexts, but here you want to avoid the cycle, which is a noteworthy goal :slight_smile:

This IMO presents a smell. Occasionally I might create a module having a verb name, and a single public function named call or execute, but I only when I want to extract some large chunk of code related to a single operation into a separate, internal module.

In this case, I’d put this function into the root context. You mentioned you want to avoid calling children from their parent, which is funny b/c I avoid the opposite. I feel that children are further subdivision of the parent’s scope, and so having parent calling them is IMO fine (e.g. boundary implicitly allows this), while the opposite indicates something suspicious in the subdivision.

Either way, if you want to avoid that, consider adding a context named Admin, or something like that.

In general, it seems that your problem is that of naming. Perhaps you have too few cases to find a meaningful abstraction for such complex operations, so stashing them in some common bucket (either root context or some vaguely named context (Admin, Management, etc)). This is a technique I often use when I can’t find the proper name. I stash things inside some common bucket until I gather enough material to recognize emerging abstractions.

To summarize, extracting operations which need to involve multiple boundaries (some of which already depend on others) to avoid cycles is IMO fine. One context per operation, with a generic call function is not something I’d do though :slight_smile:

I am also interested in trying your Boundary library to further enforce the dependency tree amongst contexts, but wonder if that would be a misuse of the intent of the library.

Nope, this is what it’s intended for :slight_smile: It even support nesting boundaries. You may want to wait until I release the next version (should be out in a few days), b/c there will be some breaking changes.

2 Likes

Thanks for the guidance. Both you and @pedromtavares have suggested placing these sort of homeless functions onto the root context which, unless I’m misunderstanding, would effectively be MyApp (not the real name, but you get it :)). I don’t actually have any qualms with this from a dependency perspective as I already delegate to those functions from this root module, but moving that code out of the services directory will allow the file structure to better match the code and push me to consider potential new contexts/groupings rather than turning into a junk drawer. When I mentioned “child” I just meant in terms of that dependency tree between contexts like People, Intercoms, Printing, etc.

I think you’re right there. I don’t have many of these yet, but can already see some potential groupings between some of them. Thanks!

4 Likes