Discussion: Don't add a database layer to your Phoenix application

This is very good question, and the only question that matters. The answer is a cliche, but it’s “to build better, more maintainable software”.

I think contexts were introduced because some people (me included) complained about inadequate separation of business logic from the infrastructure provided by Phoenix. It is awesome that the team tried to solve the problem, I am not on the side of these that think it’s a good enough solution, however.

Like @pragdave, I strongly believe that the “web” or “ui” app should be merely an interface to the underlying application - or applications most likely. Now it’s the question of how thick or thin this layer should be.

Database access layer, in my humble opinion, does not belong to the web UI interface layer for sure. It is an implementation detail, that the business logic application would use internally and behind the scenes. Ideally, this can be done using Ecto schemas that are as dumb as possible. In my app, I do not put any validation-related logic there, the Ecto schemas define just schema and relationships between schemas. These Ecto schemas also would not leak to the UI directly.

The “core” application, that provides the business logic, most likely keeps the Ecto schemas internally. The responsibility is to respond to either queries or commands issued by user, and either return some stuff (for query) or update some stuff in database (command), sending e-mails, generating reports, updating external services in the process. You can have many “core” applications in fact, which is probably good practice in most cases except simple problem domains.

How does user interact with our “core” application? They submit forms. Or they click on a link that can be mapped to empty form submit. In general case they call some endpoint on the UI web app, passing 0 or more arguments along. This input needs to be validated, business logic checked against “core” application, and if such command is looking like something user can do, it’s been passed over to “core” application for execution. The question if the modules responsible for casting forms to commands, and initializing validation is responsibility of UI or back-end “core” app is open and will vary from case to case in my opinion.

So the above is the ideal breakdown in my humble opinion, for most real life cases. I do not believe, however, that all the work above has the same weight in terms of making the software maintainable, and predictable: the most important part is separating command/form validation from underlying schemas that persist / read things from database. By doing so you no longer think in terms of CRUD app, instead of commands issued by user. You can also avoid many permissions-related bugs in the code, such as mass-assignment related issues.

Now, how to achieve that? In Rails we used to import Virtus.model to our so called “form objects”. You can do exactly the same, with Ecto schemas that are not backed up by any table. This way you integrate well with default form builder, gettext, etc. If you have an API, that layer can be totally separated from Ecto library, and either use your own, or something like vex.

So while I appreciate effort to make separation of modules in Phoenix, I think it went slightly less important way. Separating form validation from actual schemas does seem like a more important task for me. And it’s true you can easily do it on your own, but it’s not what the generators teach users to do :slight_smile:

9 Likes