How to determine contexts with Phoenix 1.3

Hi, you can check out this post. It will help you and me to understand the concept of DDD.

Feel free to share your understanding.

1 Like

No, its really not. If your notion of how to model an application begins and ends with the surface area provided by Phoenix (or Rails for that matter) then I’d urge you to dig quite a bit deeper, because you’ve got a long way to go.

Phoenix is a mediator between the interface (HTTP/websockets) and your application. It gives you a place to move data from one boundary to another. You can cram code into that seam all you want in the name of simplicity, but don’t act like nobody ever told you it was a bad idea.

Yes, there’s no middle ground between modularity-via-application and the framework-bound monolith.

I’m not altogether wild about the generator changes, but in fairness to the team, I haven’t used them much either—I just create my modules as I need them. I’m not really sure how you hold up a Rails-style application structure as the way to go on one hand, then point to DDD with the other. I think the terminology used in Chris’s keynote comes pretty well loaded and there’s a lot of people coming into Elixir and Phoenix with some degree of confusion wondering if they need to bite off the absolutely massive mental undertaking that is understanding DDD and how to apply it, and that’s unfortunate. Because the essential point is absolutely sound: Modularity is more than just a namespace or code geography, and there’s more tools to achieve it than a completely separate application.

2 Likes

In the example linked in that blog the “ridesharing” context Trip model access “indentity” context User model. Is that the way to use shared resources?

1 Like

I agree contexts are a bad idea, but for the opposite reason as @sync08. If Phoenix is just the web layer, then why is it trying to have any influence over what the rest of my application looks like? I don’t like how—if I’m not intentionally careful—Ecto manages to easily seep outside of data persistence and into other aspects of my application (I haven’t tried it yet, but Moebius seems like it could be better alternative to Ecto from an application design perspective).

To me, the ideal Phoenix and associated resources would look something like this: a Phoenix that focuses only on the web concerns of my application; a database library with a simple and intuitive syntax that does not try in any way to manage the shape of my data at any layer of my application other than between the library itself and the database (no schemas or changesets, yes on migrations); and perhaps a validation library that also does not try in any way to manage the shape of my data (although in many circumstances data validation can be done easily with only the stdlib). None of these three key libraries would make any assumptions regarding the existence of the others.

I am (of course) immensely grateful for the thought and effort that the Phoenix and Ecto teams have put into the Phoenix 1.3 and Ecto 2.0. I do feel (strongly) that contexts and schemas were not the right way to move away from models.

2 Likes

The only thing that is trying to have influence on the rest of your application (and we could discuss how much) are the generators and they are meant as a learning tool not something that decide all your stuff.

In particular, this move is exactly about that. Before Phoenix was trying to give you “models” and “Controllers” that did things. All that is out now, and it let you do as you want.

2 Likes

The generators, the to be updated guides (I assume), the to be updated books and new books yet to come (I assume), presentations from the creator specifically regarding the design of Phoenix applications, and last but certainly not least the community.

None of these things are physically forcing my fingers to hit my keyboard in such a way that they produce a context-schema Phoenix application, but they have strong undeniable influences on the support and development of resources and an environment favorable specifically to the context-schema application design.

2 Likes

How do you handle common models. Like registration would create a user, which would then be used in auth, ticketing etc. Do you have a User schema under Auth which is then used by other contexts?

3 Likes

I’ve a custom User schema for registration, which does hold information e.g. related to email confirmation opt-in. That one is not used by any other contexts. There’s also a User schema in auth, which does use the same table as the registration one, but loads different data. That one is used for auth and ticketing and such, so basically for everything done by the “current user”. But besides for the login/auth plug I’m not even concerned with the auth context from the phoenix pov. I’m simply passing the current user into the ticketing context and it’s retrieving the needed data of of the struct. So there’s not really interdependency between the contexts, but it’s rather passing data around. It’s like when I pass the ticket data to the pdf context to let it render the ticket voucher.

2 Likes

So you are using the same table but different schema definitions. I suppose i need to try it out to come up with a better understanding.

1 Like

As @DianaOlympos said, Phoenix already ticks those boxes. The thing you are complaining about as dictating how you write your applications are learnings tools to help push folks in the right direction as they are ramping up. You are right that these ideas from the generators will permeate outside, but if “well named modules and functions, with clear isolated boundaries” is what ends up having “strong, undeniable influences” on folks code, then MISSION ACCOMPLISHED :dancer:

I think Phoenix 1.3 actually aligns with your desires if you take a step back. Phoenix 1.3 is all about making it clear that Phoenix is just the web interface to your greater application. Along with that, to have a “web interface”, you need something to interface with, namely well-named modules and functions that accomplish the concerns of your domain. This is where the generators and contexts come in, to help push folks along as they are getting up to speed. If you don’t like particularly features like the generators – don’t use them. If you aren’t happy with Ecto, use something else, Phoenix does not care. All integration with Ecto happens via our FormData and Param protocols.

4 Likes

How do you specifically handle other data that references the user?

Do you still have foreign keys and use the user_id approach as mentioned by Jose?

1 Like

Contexts had me confused as well, I still don’t understand them fully. I’ve settled on something simple that works for our application and is good enough.

When starting new application, I needed a simple CRUD for users. To jump-start, I used:

mix phx.gen.json UserAPI User users email:string password:string scopes:array:string

Among other things, I got this folder with two files:

lib/my_app/user_api/user_api.ex
lib/my_app/user_api/user.ex

I found that having context with a name UserAPI is an absolutely amazing starting point, because:

  1. File user_api.ex is now my dump pipe for all user-related code,
  2. schema file user.ex remains clean and un-touched,
  3. I use the user_api.ex dump pipe until I figure out some of the code is good to go into a module of it’s own,
  4. As soon as I figure out something needs to be extracted, I created a file nested under user_api; recently I added a file called user_api/search.ex that incorporates validating search params & building complex search queries for my users,
  5. My user_api.ex is now a glue between repo & user_schema and the rest of the app: by our convention no module should be calling functions from MyApp.Repo/user_schema directly.

This maybe be very dumb and simple, but it works perfectly for us.

6 Likes

The answer anytime you start talking about how to design software in general (and with DDD in particular) is always it depends, which is terrifically unsatisfying but also true. Its really going to depend on your features and domain, but there’s background to that question worth considering. We often talk about software that exhibits loose coupling—different parts that are only loosely related can change relative to each other with effort that is roughly proportional to the degree of coupling between those two parts. Its pretty easy to see that if you embed a lot of references to something like a user schema in all parts of your data schema, if you need to make radical changes in something like authorization (say, you start supporting an OAuth signup flow that does not collect email) then all the parts of your software that depended on a User schema looking a certain way are suddenly going to have to adapt.

Something we hear about less often is the idea of cohesion, that all the parts that touch each other belong together, and conversely, that parts that don’t have overlapping or complementary responsibilities are not bound together. Imagine that you’re building an application for a warehouse, working on an inventory tracking system that is intended to keep track of stock levels and reorder new stock when it gets low. Is it relevant to that system that a pallet was delivered by UPS instead of FedEx or a custom shipping company? (Again, the answer is it depends, but lets say for our purposes its not.) The UI in the final application should be able to show, for any given stock item, the details of its arrival in stock. Now, should the stock on that pallet be noted with an association to a particular delivery service?

If you’re building a monolith, majestic or otherwise, you may well be inclined to say yes. In doing so, however, you’re stepping outside the lines of your module with concerns that are not relevant, and in doing so reducing cohesion and creating tight coupling between that module and another (in this case, we’ll say its the shipping module’s responsibility to know that information). Shipping may need to know the skus and how many of each kind of item are on an incoming pallet, but it doesn’t need to know where its stored in the warehouse. So in each case, it may be worth decorating the item with metadata (for example, a user id, or a pallet source id) but if you’re aiming for really high cohesion, you almost certainly don’t want to extend the metadata beyond that and encourage consumption of data from outside your boundary.

And as has been noted before, what’s really being discussed here are a set of conventions that the Phoenix team is encouraging through the use of generated code. You can do all of this without the generators. You can also skip all of this by not using generators. Elixir doesn’t care what you call your modules. It doesn’t care where you put them as long as they’re compiled. Wherever you are in your app cycle, just put a stake in the ground and start moving from there. You can develop new features using the pattern the Phoenix team encourages. Move your old one over time. Or jump in with both feet, or not at all. One of the great things about convention over configuration in Phoenix as opposed to Rails and its ilk is that where Rails makes it very painful to fight conventions a lot of the time, Elixir and Phoenix reduce that friction a great deal.

/end_soapbox

8 Likes

I really appreciate you joining in this thread, Chris. I feel as though my earlier reply comes across as way more negative than I hoped and I actually feel terrible for the way I worded it.

I do still think the whole context idea is causing some issues in the community though. Not because it’s a bad idea (the intentions are very admirable) but because there is still no agreement on best practices. We all know we should structure our code better and need to do so using proven best practices. Well named modules and functions is something we can all agree on.

I think the DDD terminology is hurting more than helping. I’ve been reading a lot about it over the last few months but I don’t buy into the idea as it’s not a solution to any of my problems. It just doesn’t feel like it’s suitable at all for web apps.There is also literally zero consensus on the subject. This is a personal thing so feel free to ignore this.

Thinking of contexts as a place to store modules and functions helps understand them but I don’t think adding artificial boundaries between them makes sense. I’m not going to create two tables for “blog_user” and “shop_user” and try to keep them in sync. I’m going to have a single user that is fully aware that they have both blog posts and shop items. That means a user schema (in an account context) will have relationships to other contexts’ schemas. I’m hoping Jose can chime in with his reasoning why this is a bad idea.

I’ve been using asp.net core for the last year and have actually come to really like the structure. I have a main models directory (I’m not even going to get started with the aversion to models :stuck_out_tongue_closed_eyes:) with all of my core domain models in one place. So things like “user”, “blog”, “post”, “comments”, “department” and “business”. I then structure everything into areas so the “blog” area will have it’s own controllers, views, view models and services so it is its own mini app. The key to this is the view models which are representations of the actual domain models which can have their own validations, fields e.t.c. but are cast to domain model before going to the database. So like Ecto schema and changesets.

1 Like

If it doesn’t solve any of your problems then so be it. Nobody is keeping you from putting all your domain logic in a single place. If your domain is small enough it might work out without problems. I think the bigger “improvement” for phoenix 1.2 → 1.3 is pushing people to not have domain logic in the web controllers, which is the far worse thing to do. Contexts are (at least to me) rather a hint to people, where the journey can go as soon as things start to grow and one is feeling the issues with having to handle all sorts of different concerns in a single place.

Probably the real hard thing here is just that it’s difficult to cater to people of all sorts of skill levels / all sorts of project scopes with just a single kind of generators. The 1.2 generators were the quick&dirty jump-starters, the 1.3 are the opposite direction, where you’re supposed to think a bit about your application before you do run into the problems of outgrowing quick&dirty.

2 Likes

Thanks for the thorough answer.

I guess my conclusion will be that my app currently is small and simple enough that everything can live in the same context.

1 Like

It is not a DDD terminology. Chris mentioned it during one slide, in one talk, to provide some reference for folks familiar with DDD. A context is a module and functions, that’s it. Don’t bring more to it than it is.

As I mentioned on another thread, if people take whatever pattern or restriction and follow it blindly, this change is for nothing, because it will likely end-up on another dead end. If you want to have cross-context relationships, think about pros and cons carefully and move forward. Different teams will balance trade-offs differently.

At the same time you say there is no agreement on best practices, you took a “best practice” comment and disagreed with it. :slight_smile: That’s the point. Best practices are guidelines. They are very important for new teams and we will provide more of them to those teams as we go but experienced teams should re-evaluate them when necessary.

5 Likes

One more comment on this.

First of all, Elixir is not Ruby and Phoenix is not Rails. Ruby, opposite to Elixir, does not provide any mechanism for structuring your projects. Most of the standards you see today in Ruby came from the community.

In Elixir, we do have such structure. Elixir itself has the concept of applications and how code is structured. Those are pushed as part of the language and its tooling.

When we say Phoenix is not your application, it is not some anti-Rails instance. So don’t make it so. It is literally how writing projects work in Elixir (and Erlang/OTP). We had technical reasons for generating the “web” directory in previous versions but those have been solved, so Phoenix projects should follow the standards in the community instead of fragmenting it.

The way we are advocating Phoenix applications to be structured is exactly how community projects such as Elixir itself, Phoenix and Ecto are structured. In Ecto, we have “contexts”, such as Ecto.Repo, with private modules inside, such as Ecto.Repo.Supervisor. Only Ecto.Repo is allowed to talk to Ecto.Repo.Supervisor. Sometimes we do violate this rule by making an inner module public but that’s exactly the point: building a project with Phoenix should be no different from building any other Elixir app.

11 Likes

But there would always some cross-context relationships. Like say we have auth, registration, orders context. Registration would create a User, which would be used by Auth and Orders would be associated with Users. Do we create User schema in each of the context, have a main schema say in Auth and associate it with others? I assume it would need to be a single table. I am trying to understand these kind of relationships wrt contexts. Some simple examples would be very useful.

3 Likes

@pra just to clarify, if they are in the same database, then you definitely want to have foreign keys at the database level. At the application level, you will have to associate them too. The question is only if you have are going to have has_many/belongs_to going across schemas contexts.

The major downside of doing so is that you can easily end-up with really large schemas that associate with many schemas in different contexts. If you decide to not have has_many/belongs_to across contexts, then some parts of Ecto will become slightly more verbose. It means that instead of:

Repo.all user.posts

You need to write:

Repo.all from Post, where: [user_id: ^user.id]

Is it worth it? I would say so. But you can be the judge in your application. Relationships across contexts will certainly exist, it is a question of how you will reflect it on the application side.

4 Likes