Contexts

Phoenix 1.3 introduces the concept of contexts. These are somewhat inspired by bounded contexts in domain-driven design. They provide a way of defining clear boundaries between parts of your application. A context defines a public API that should be consumed by the rest of the application.

Bounded Context is a central pattern in Domain-Driven Design. It is the focus of DDD’s strategic design section which is all about dealing with large models and teams. DDD deals with large models by dividing them into different Bounded Contexts and being explicit about their interrelationships.

Martin Fowler

Bounded context - by Martin Fowler
Bounded context - by Martin Fowler

Contexts in Phoenix

When using the included phx.gen.* generators you must provide an additional context argument for each resource you create. As an example, when generating a JSON resource:

$ mix phx.gen.json Accounts User users name:string age:integer

The first argument is the context module followed by the schema module and its plural name (used as the schema table name). The context is an Elixir module that serves as an API boundary for the given resource. A context often holds many related resources. It is a module, with some implementation modules behind it, that exposes a public interface the rest of your application can consume.

This is powerful, because now you can define clear boundaries for your application domains. You’re now implementing your application, containing your business logic, separate from the Phoenix web interface. The web interface is merely one of the possible consumers of the API exposed by your application using a context module.4

The official Phoenix documentation has a guide detailing Contexts in further detail.5

Contexts in Conduit

Given the features that we plan to implement in Conduit specified in the previous chapter, we can define the following contexts to provide a boundary for each part of our application.

Accounts Register users, find user by username.
Auth Authenticate users.
Blog Publish articles, browse a paginated list of articles, comment on articles, favourite articles.

Why would we separate the Conduit functionality into three contexts? To keep the responsibilities cohesive within each context. For example the responsibility of our Accounts context is to manage users and their credentials, not handle publishing articles. Therefore we have a blog context, separate from accounts, to publish and list articles.

Contexts have their own folder within lib/conduit which immediately shows at a high level what the Conduit app does and allows easy navigation to help locate modules and files related to a specific area of functionality.

  • lib/conduit/accounts
  • lib/conduit/auth
  • lib/conduit/blog

With this separation of concerns enforced at the directory structure, when I need to add a feature related to blogging, or fix a bug for articles, I can immediately focus on the lib/conduit/blog folder and its contained modules. This reduces the cognitive load when working with the code.

These contexts would provide public API functions such as:

Accounts
  • Accounts.register_user/1
Auth
  • Auth.authenticate/2
Blog
  • Blog.publish_article/1
  • Blog.list_articles/1