I want to have a discussion about contexts using an example that’s a bit more complicated than what I’ve seen on the forums so far. I’m still trying to wrap my head around contexts; specifically when to include functionality within a given context or when to break functionality into its own context. I’m hoping that this will serve as an exercise for the community to help people (like myself) better understand contexts (and possibly turn into a guide if this turns out well). Since I assume there’s always more than one way to structure contexts within a project, I’m looking forward to seeing different solutions.
I want to keep the conversation more high level, so I won’t be doing too much (if any) code. I also want to create a fairly complex example to give us more room to play with contexts. Here’s a summary of our example application:
The website is a social networking platform that allows users to sign up with an e-mail address and password. Users can follow other users, make posts to their own page, make post to another user’s page, comment on a post, and react to a post. A user’s profile page shows basic info and a list of recent posts made by themselves or by guests. A user can see all the most recent posts of the users they follow on their home page. There can be “pages” that are moderated by users (such as a page representing a company) that have the same posting/following/reacting functionality that a user has. A user must have permission to act on that pages behalf. All other site functionality is standard, such as a settings page to edit their account or profile.
I’ll take a stab at creating a context within this application. I’m also going to ask questions, show my thought processes, and provide multiple solutions for the same functionality. Feel free to answer, comment, critique, or correct anything that I do. Again, I’m still learning contexts, so I’m sure my solution is far from optimal. If you want to show how you’d model the same context I present or want to show how you’d model a different context, please do so. I want this to be an educational discussion on contexts and how to build them in a more realistic application.
To keep this post short, I’m going to focus on a small subset of the application. Specifically, I believe we could split the user information into two contexts, Accounts
and Users
. The Accounts
context would be for “private” or application-centric information while the Users
context would be for “public” information (that is, information shown on a user’s profile). I’ll be talking about the Accounts
context specifically.
The Accounts
context would be responsible for authentication, email verifications, password resets, permissions, registration, and sessions. The folder structure might look like:
/accounts
/authentication
log_in.ex # embedded schema
/password_reset
request_reset.ex # embedded schema
reset_password.ex # embedded schema
accounts.ex
authentication.ex
email_verification.ex
password_reset.ex
permissions.ex
registration.ex # embedded schema
session.ex
update_email.ex # embedded schema
update_password.ex # embedded schema
The embedded schemas represent a “form” that the user must fill out in order to perform certain actions on the website. These forms don’t necessarily map one-to-one in regards to the underlying database implementation. For example, the log_in.ex
embedded schema would require e-mail and password, the request_reset.ex
embedded schema under /password_reset
would require a verified e-mail address, the reset_password.ex
would require a password and its confirmation, and the registration.ex
embedded schema would require the user’s name, e-mail, and password. All of these fields could be store across one or multiple databases. These files deal more with the interface between the web application and our business logic, making sure the website doesn’t dictate how we structure data in the database.
Now, I don’t know if it’s considered correct to structure the embedded schemas the way that I have. I could’ve easily just included them all in the root of the /accounts
folder with more descriptive file names rather than creating a nested folder structure. For example, /accounts/password_reset/request_reset.ex
could’ve simply been /accounts/request_password_reset.ex
. I could’ve also just created a nested folder for all embedded schemas instead of spreading them out across multiple folders. For example, the folder could’ve been name /accounts/schemas
, /accounts/forms
, or /accounts/actions
and held all embedded schema files within it. How would you structure this context?
Each file would be responsible for its own functionality. For example, email_verification.ex
would contain all the logic necessary to verify an account’s e-amil address.
Now, the Accounts
context could expose all of its functionality through the main module named Accounts
. In this case, all functions in separate files, such as email_verification.ex
, would be referenced in accounts.ex
in some manner, such as defdelegate
. A short, incomplete list of functions might look like:
Accounts.log_in
Accounts.has_permission?
Accounts.create_session
Accounts.mark_email_verified
Accounts.register_account
Another option would be to namespace the functionality. Using this method, there would be no need to reference functions within the accounts.ex
file. A short, incomplete list of functions might look like:
Accounts.Session.create
Accounts.PasswordReset.reset_password
Accounts.EmailVerification.mark_verified
In @chrismccord’s recent talk on Phoenix 1.3, he mentions that you shouldn’t namespace a module if it’s leaking implemention details, such as using Amazon’s SQS service. However, these are all more general purpose namespaces that have more to do with sectioning off functionality than revealing any implementation details. Because of that, it makes me wonder if such functionality should be (or could eventually in the future be) split off into its own context. Technically speaking, we could make several contexts out of this, including but not limited to Authentication
, EmailVerification
, PasswordReset
, Permissions
, and Sessions
. Would it be best to keep any, some, or all of these within the Accounts
context? Would you split them apart?
Well, this post is already getting quite long, so I think I’ll stop here. Does this look like a reasonable use of context given our example application? If I did absolutely everything wrong, feel free to say so. Would anyone like to take a stab at creating a context for the application? It could be any part of the application, including the Accounts
portion.
Also, I know I didn’t discuss it above, but I was assuming that the application only uses a single database managed via a library like Ecto. I know there’ve been questions about how to manage the database across contexts. Perhaps someone might be able to say how they’d approach that aspect of the application. For example, I’ve read that a few people on this forum has suggested a “shared” folder for the base DB schemas that all the contexts can use.
Anyway, I’m hoping this will be a great collaborative community effort to help everyone come to a better understanding of contexts.