Transactional consistency in a domain isolated umbrella app?

I’ve been working on an umbrella app where I’m trying to decouple and separate the domain logic (Ecto schemas, queries and repo calls, etc) from the Phoenix backend (controllers, channels, views, etc).

My contexts serve as the public API for my controllers to retrieve information from the database. However, as my view requires more info from the database, my controller needs to retrieve many loosely related pieces of information from the contexts with transactional consistency. This leads to either bloated transactions with a Repo call in the controller, or many very specific, large context functions written solely for a controller’s specific use case.

I would like to avoid my contexts from knowing specific requirements of my presentation content, and would like to prevent my controllers from becoming large with business and presentation logic and from making Repo.transaction calls. This seems contradictory :confused:

Any ideas/advice?

1 Like

This might (but not necessarily does) mean your contexts are too granular and it might help to put some of them together into bigger ones or split them differently.

@yurko An example context function I have is : Accounts.get_paginated_posts_for_user_order_by_date/2

This seems very specific to the controller’s use case as-is. For my context to satisfy all of my controllers’ requirements (stat aggregation, post pagination, etc), I would have to make it something like Accounts.get_posts_info/2 which does a poor job of defining what the function does (because it does so many different things). This is my dilemma.

Well, if it’s about function names I’d go with the ones that have meaning for the outside world and do not expose internals, like list_posts_for_something which might do some ordering etc, this way if for example you ever switch to custom order field for ordering, you wouldn’t have to change function names or live with get_paginated_posts_for_user_order_by_date that doesn’t order by date. If you do that differently in different places then just pass the ordering field as parameter with some sensible default.

Though unless I misunderstood something, you meant you call many contexts as part of transaction in your controllers and your example states Accounts context that delivers posts which probably need their own context, something like Blog.list_by_user(user.id).

1 Like

What if you separate the database app into its own “otp” app, and have contexts return ecto multies, which are then passed to the database app from your controllers, and the result is handled in the controller?

That’s an interesting thought. Would that be equivalent to moving ‘repo.ex’ to a new app and using ‘Repo’ from the controller?

1 Like

Depends on what’s in repo.ex and what is Repo.

At this point in the game it may make more sense to simply start a new, “coarse granularity” context. That context then creates a new Ecto.Multi and hands it to each of the “fine-granularity” contexts in turn to add their bit to the Ecto.Multi. That way:

  • The details still remain with the fine-granularity contexts
  • The coarse-granularity context deals with the Repo/Multi
  • The knowledge of the coordination is kept out of the controller

If it’s time to move to a separate OTP application, all the business logic should move, only leaving behind the web interface concerns with Phoenix.

Example