Contextual schema VS out-of-context schema (in Phoenix 1.3)

These two days I have been reading Phoenix 1.3. Learnt that the new generator by default puts schema inside contexts. After reading the post from @michalmuskala Putting Contexts in Context, I agree having context as module is a good idea. But is it worthy to have contextual schema?

I guess the benefit of putting schema inside context is similar to rom-rb which will have fewer couplings between database table and model schema. E.g. model schema can be just a partial of database table schema. And the disadvantage will be harder to define cross-context associations.

The best practice to define cross-context associations either is still in a grey area or will never exist (because YMMV). According to Putting Contexts in Context, the approaches to define cross-context associations will be:

  1. Don’t use join at all. β€œSo, for example, instead of having an association, you’d only store the id. You can still access the data using the public interface of the other context.”
    Cons: N+1 queries and worse performance.

  2. β€œHaving schema in each context reading from the same table (each having access to mostly different fields)”
    Cons: fragmented schema and no single source of truth (sounds like how spaghetti begins).

  3. β€œHaving multiple tables that use the same primary key value (so you don’t have to keep a separate foreign key around).”
    Cons: dirty hack around.

Since performance is one of the major reasons that makes me choose Elixir & Phoenix, I will opt out option 1. And I personally hate option 3. So last standing option 2.

Therefore to me, contextual schema VS non-contextual schema is a tradeoff of more couplings between database table and model schema VS fragmented schema and no single source of truth. I personally hate fragmented schema and no single source of truth more. Hence, an out-of-context schema structure (or database-coupled schema structure) might be more handy for my general projects.

For example, if I am making a car dealer app, Phoenix 1.3 will give me contextual schema structure like this:

Contextual schema example

CarDealerApp
└── contexts
    β”œβ”€β”€ admin-edit-product
    β”‚   β”œβ”€β”€ interfaces
    β”‚   β”‚   └── update-product
    β”‚   └── schema
    β”‚       └── product
    β”œβ”€β”€ explore-product-plans
    β”‚   β”œβ”€β”€ interfaces
    β”‚   β”‚   └── list-plans
    β”‚   └── schema
    β”‚       β”œβ”€β”€ plan
    β”‚       β”œβ”€β”€ product
    β”‚       └── user
    β”œβ”€β”€ explore-products
    β”‚   β”œβ”€β”€ interfaces
    β”‚   β”‚   β”œβ”€β”€ list-products
    β”‚   β”‚   └── show-product
    β”‚   └── schema
    β”‚       β”œβ”€β”€ product
    β”‚       └── user
    β”œβ”€β”€ get-product-plan-quote
    β”‚   β”œβ”€β”€ interfaces
    β”‚   β”‚   └── create-quote
    β”‚   └── schema
    β”‚       β”œβ”€β”€ plan
    β”‚       β”œβ”€β”€ product
    β”‚       β”œβ”€β”€ quote
    β”‚       └── user
    └── registration
        β”œβ”€β”€ interfaces
        β”‚   └── create-user
        └── schema
            └── user

In contrast, the out-of-context schema I propose will look like this:

Out-of-context schema example

CarDealerApp
β”œβ”€β”€ contexts
β”‚   β”œβ”€β”€ admin-edit-product
β”‚   β”‚   β”œβ”€β”€ changesets
β”‚   β”‚   β”‚   └── to-update-product
β”‚   β”‚   └── interfaces
β”‚   β”‚       └── update-product
β”‚   β”œβ”€β”€ explore-product-plans
β”‚   β”‚   └── interfaces
β”‚   β”‚       └── list-plans
β”‚   β”œβ”€β”€ explore-products
β”‚   β”‚   └── interfaces
β”‚   β”‚       β”œβ”€β”€ list-products
β”‚   β”‚       └── show-product
β”‚   β”œβ”€β”€ get-product-plan-quote
β”‚   β”‚   β”œβ”€β”€ changesets
β”‚   β”‚   β”‚   └── to-create-quote
β”‚   β”‚   └── interfaces
β”‚   β”‚       └── create-quote
β”‚   └── registration
β”‚       β”œβ”€β”€ changesets
β”‚       β”‚   └── to-create-user
β”‚       └── interfaces
β”‚           └── create-user
β”œβ”€β”€ schema
β”‚   β”œβ”€β”€ plan
β”‚   β”œβ”€β”€ product
β”‚   β”œβ”€β”€ quote
β”‚   └── user
└── validations
    β”œβ”€β”€ plan_must_be_avaliable_in_region
    β”œβ”€β”€ product_must_be_active
    β”œβ”€β”€ quote_expiry_date_must_be_before_plan_expiry_date
    └── user_must_be_vip

Basically, I would still keep changesets contextual because it’s very common that contexts require different fields, etc. On the other hand, I would abstract the specific validations out of context since often they will be shared across contexts. validations folder structure can be either flat or grouped by schema name, it can make sense in both ways and does not really matter.

What do you think?

7 Likes

I dont understand why everything needs a generator. Why don’t let let people handle contexts as it fits better their applications. I think Phoenix doesnt need to handle database at all.

I would add to the schema a basic changeset(to ease cast_assoc) with the validations that are made in the database, not null fields, string sizes, things like that.

2 Likes

I can see your point. I agree generator is less important to experienced users who know stuff inside out. However, if Elixir on Phoenix really wants to be popular/mainstream like Rails, I believe convention over configuration is the bottom line that should be aimed for. Some benefits:

  1. make junior developers productive,
  2. to convince boss Phoenix and Elixir are good tools. Because new employees can pick up existing codes real quick since codes all follow common patterns like in Rails.

I thought about putting basic type validation changesets in schema as well. I can reason with this approach because schema is the guy who defines data structure, in that way, it makes sense to let schema handle type validations. I did not do that because it might open a door to make developers think it’s okay to use schema like OOP model and put more lifecycles in schema changeset. However, if you trust in developers’ discipline, this won’t be a problem. Another minor downside, now you will have separate changesets in schema and context, a bit more indirections.

3 Likes

The problem for me is living in the shadows of Rails, one of the things that I loved on Phoenix 1.3 is leaving the model pattern. Making the contexts the new model wont change anything.

CoC wasn’t a thing when Rails appeared, I remember people saying that it wouldn’t work because they need to be in control of their application and things like that(I know it’s anecdotal evidence and it doesn’t mean anything) but that make sense to people which Rails was aimed for. The point is to define the kind of public Phoenix is aimed for. I think Phoenix shouldn’t be aiming to the same public Rails is aimed, people that uses Phoenix today are the ones that Rails doesn’t fit. The same way Elixir isn’t Ruby, Phoenix shouldn’t aim to be Rails and that means that it doesn’t need to aim for the same public. The same way that are a lot of gems that have their generators and do things that isn’t covered by Rails generators, maybe Phoenix should leave contexts loose and see where the community goes and that doesn’t mean that Phoenix isn’t doing CoC just means that it isn’t omakase… :wink:

1 Like

I don’t really understand this part. Generally speaking, I support the concept of context. And I don’t see out-of-context schema will make context into a new model.

IMHO the problem with OOP model is that we have lifecycles and too many responsibilities (aka interfaces) defined in a model. Out-of-context schema does not introduce any of these mistakes back to context.

Golden. I’m sold.

I guess my biased subconscious had blinded my eyes. I hoped Elixir ecosystem will become as big as Ruby’s. Now think back, it’s just not very practical. FP and OTP concepts are naturally harder to learn. Even old famous Haskell, Scala, Closure cannot get close to rank 30th in popular language list. Elixir should not be expected to be the next Ruby in term of popularity.

Anyway, this is a bit off topic.

I think generator or basic code guide is still important at least for educational purpose. It will be extremely unfriendly to newbies if you don’t have any example for basic structures at all.

1 Like

:point_up_2:Totally agree

1 Like

Oh sorry, I wasn’t thinking about context in the way you presented here, but the way it’s working right now. My bad :disappointed:

I agree with you, and that’s what I meant with β€œmaking the contexts the new model”. If Phoenix enforces a way to deal with it that early, it will guide the whole community to it’s path. That’s exactly what happened with models in Ruby world, ActiveRecord/Rails generators enforced a way to do models that leaded to be a messy bloated thing.

I think when I said β€œI dont understand why everything needs a generator.” it looked like I see them as unnecessary, but i dont. My point is to let it loose, see where the community goes. I think Elm is a good example of that, the Elm Architecture that is a common place in the whole Elm community now was something that was not enforced but came from people that used it a lot. It was so good that influenced things outside Elm community, like Redux.

1 Like

Uh huh. You basically want to fight against path dependence. I haven’t thought about this from such a high level yet.

My dad once told me in Chinese - ordinary people follow common sense, genius challenge common sense. So you want to find a way to make everyone genius. That’s admirable.

But translate to real life this will be - don’t educate people, let them find their own way. Not saying this is a bad thing. But it will be a very bold move.

1 Like

I read about generators in this topic … And I hope that anyone who wants more of them will like this new project:

New generators based on Rails and NestJS for Phoenix.