Lately, there has been quite some discussion about the new ‘Contexts’-feature that is going to be added to Elixir’s web framework Phoenix in version 1.3.
Some people are confused by them, some people believe ‘they are terrible’, some only believe that there needs to be a little extra information of how to use them in practice.
So, here are my two cents on how to approach contexts.
Why care about contexts?
A little context:
Lets look at some design choices that Elixir, Phoenix and Ecto have made in the past:
- In Functional programming, the data and the functionality are decoupled.
- In Elixir pipleines, each step is explicitly named, rather than invoking ‘a method on whatever came out of the previous step’.
- Elixir’s Behaviours and Protocols allow very strong decoupling between interfaces and their implementations.
- In Ecto, the schema (datatype structure) is decoupled from calls to the actual database.
- In the Ecto database mapper, validations are part of the updating process, rather than being intrinsically coupled to the data structure you store inside your database tables.
Notice a pattern here? The pattern I am hinting at is decoupling. By creating separations between the different parts of your software, you create explicit interfaces, which can be separately understood, tested, refactored and maintained.
Face it: The software we build today will have different requirements tomorrow. If you want to be smart, then plan for that and write code that can easily be adapted in the future.
This does not mean to make your code ‘overly generic’: At the end of the day, you are also facing very real deadlines today, after all. But by performing some planning on how you are going to solve a problem and keep your code maintainable and loosely coupled really helps to ensure that you will also be able to work with tomorrow’s changed world.
Phoenix is a Web-framework following the classic approach of the Model-View-Controller (MVC) code structuring (decoupling!) pattern. MVC is very much intended to ensure that the different parts of your code have single, non-overlapping responsibilities.
If structured properly, this is what the different parts do:
- View: This is the presentation layer, where the final textual response to the user is constructed. It should therefore only concern itself with presentation logic, not business domain logic, not authentication or dispatching logic.
- Controller: This layer handles the flow of a user moving through your pages: Redirection, authentication, handling success or error results. It should not concern itself with how something will look (part of the view layer), or do any business logic itself. Controllers should be nothing more than ‘traffic cops’.
- Model: Against common belief, an MVC-structured application only has a single model: The Abstract Conceptual Model, which is the abstract representation of your business domain.
Frameworks in the past (* cough * Rails * cough *) confused the term ‘model’ to mean 1:1 representations of the entities you want to store in a relational database, complecting three separate stages:
1. The way you want to persist data.
2. The way you want to represent data in your system at runtime.
3. The logic to create, look up and transform the data you have.
In a framework structured like that, the only possibility to build your business domain logic would be to create an intricate choreography between Controller and ‘Models’ layer. This results in code that quicly spirals out of control and turns into spaghetti code. In the three years of experience I have with projects in Rails and some other webframeworks, I have seen this happen time and again.
Database Logic is not part of MVC. I will repeat that: Database Logic is not part of MVC.
Rather, what kind of persistence (if any!) your software service requires is an implementation detail of your business domain conceptual model. This is closely related to the statement “Phoenix is not your Application” that has been repeatedly said lately. What this means, is just that Phoenix, as web-facing interface of your application, is just a consumer of your business domain logic. This is exactly what the Dependency Inversion Principle is all about.
In any case, Back to the ‘Model’. Most likely, your business domain does not care at all that some information about it is shown in a web-interface, and that some of the data inside your business domain conceptual model might be altered through that same interface.
Rather, your business domain conceptual model only cares that it is possible to read data about it, and that it is possible to perform certain updates.
So, just put the interface of your Model elsewhere. Not in your controller code, not in your view code, not directly in the code of the datatypes you might want to persist.
Just create a new module, with a couple of functions that expose the functionality the business domain contextual model should have to interact with it from the outside world.
Tadaa! You have just created a context.
A context is nothing more, and nothing less, than a module with functions that clearly indicates how your business logic and the outside world (such as, but by no means limited to(!), a web interface).
This helps by making your code more loosely coupled, easier to reason about, easier to test. Ergo: more maintainable.
So… how many contexts should my application have? What are they? Where are their borders?
If you start out with a new application, I believe you should start out with just a single context: Just create a single context (named
Model or maybe
Cars if you are building something that sells cars). The modules making up the internal details of the context should probably best be put in a separate folder, which you would give the name of the context.
If you start out and you already believe that your application contains multiple contexts, then there is a high possibility that you are building something that is a lot larger than a Minimum Viable Product, which is probably not what you (or what management) actually desires.
After the first version of your application is done, you will be able to see what parts of your context are closely related and which part are actually not related at all. If desired, unrelated parts can now be split off to their own contexts (As long as the resulting contexts do not contain circular dependencies, you’re on the right track). You are exactly able to do this because you wrote out a clear API of your software service’s business logic.
So, to summarize: Don’t overthink it. Create a separate module to put your business domain logic in instead of inside your web-controllers, and by doing this you are able to alter either part later on without worrying about how this affects the other.
A context is just a module, with some functions. Internally, a context module might call other modules with other functions. But the consumer(s) of the context do not care about that.
Looking back on my years as software developer so far, I believe I currently finish a constant amount less functionality in the same amount of time as I did a few years ago. But what I produce is exponentially more useful, adaptable and maintainable. And that, I believe, is rather nice.
So there you go. Contexts are not a law set in stone. They are a helpful tool, a hint to guide you to write more maintainable code. Whether you want to use contexts and exactly how you will shape them is completely up to you. The most important truth remains: Think about your code.
The opinions in this article are the opinions of Wiebe-Marten, and do not necessarily reflect the opinions of Panache as a whole.
P.S. I apologize for all the bad puns.