Best practices when dealing with large contexts or having many contexts? Running into road blocks following the guides / books

Earlier today I asked a question in the public Slack channel and it resulted in hours of conversation about how to break up a context, and multiple people had different opinions.

This makes me think that most people really enjoy the idea of having contexts around, but the documentation, generators and the Programming Phoenix 1.4 book do not make it clear on how to use them beyond very tiny examples.

As of Phoenix v1.4.8 the documentation and generators produce code like this:

$ mix phx.gen.context Accounts Credential credentials \
email:string:unique user_id:references:users

* creating lib/hello/accounts/credential.ex
* creating priv/repo/migrations/20170629180555_create_credentials.exs
* injecting lib/hello/accounts.ex
* injecting test/hello/accounts_test.exs

The basic idea is:

  • You have a single entry point into your context as lib/hello/accounts.ex.
  • You have a lib/hello/accounts/ directory to hold any files that your context needs.

There’s also the example application in Programming Phoenix 1.4 that follows this style.

Problem 1: A large context ends up having 1 public module with too many functions

The problem is, what happens when you have a context that has more than a small handful of files and only a few functions that you want to publicly expose?

Let me give you a real example based on an application I’m developing as we speak. Which is a course hosting platform.

This was a 2nd pass attempt of coming up with a courses context using the rules provided by the documentation, generators and book written by the creators of Elixir and Phoenix.

courses/
  course.ex
  landing_page.ex
  overview.ex
  package.ex
  section.ex
  lessons/
    lesson.ex
    note.ex
    transcript.ex
    video.ex
    attachment.ex
    lesson_attachment.ex

From an admin’s POV, almost everything listed there would be CRUD’able and from a “student”'s POV (the person watching the course), all of those things are pretty tightly coupled from a business sense.

For example, a student would watch a course and then be presented with a table of contents with sections and lessons, and each lesson has videos, transcripts and attachments.

I’m not done coding the platform yet so I can’t give you an exact line count, but if you had a single lib/hello/courses.ex file as the sole public interface for this context then I would bet you would end up with over 100 functions and thousands of lines of code. That makes it very difficult to reason about IMO, but that’s only problem number 1 with the above set up.

Problem 2: The lib/hello directory gets overloaded with a grab bag of files

The second potential issue with the lib/hello/courses.ex style is what happens when you have a bunch of different contexts? I’m barely into creating this project but I already have contexts for accounts, checkout, payments, enrollments, courses and questions. I’m for sure going to have many more things too (progress tracking, affiliate systems, and the list goes on).

What’s going to eventually happen is the lib/hello/ directory is going to have Phoenix specific files like application.ex and repo.ex but now it’s also going to have 15 different context entry point modules and potentially other modules that are related to my project but aren’t contexts or Phoenix specific.

That makes looking into the lib/hello/ directory a very crazy experience where you’re overloaded with many different types of modules and we’re back into a big mess that’s comparable (but not as bad) as the old “models” directory.

I’m already experiencing this problem with only 4 contexts that I have implemented when they are all mixed into the other modules in that directory.

A potential solution

After hours of discussion and input from others (special thanks to @LostKobrakai for having a really long convo and providing valuable input about my domain), we kind of decided on this:

lib/
  hello/
    accounts/
      accounts.ex
      credential.ex

So the basic idea is, problem number 2 is immediately resolved because your context entry points are not exposed in lib/hello/accounts.ex. Instead it’s in lib/hello/accounts/accounts.ex (the public module is the same name as the directory).

Problem number 1 also goes away because you’re no longer limited to a single entry point for your context. For example, using the courses example from before, you could now end up with this instead:

courses/
  courses.ex
  course.ex
  landing_page.ex
  overview.ex
  package.ex
  section.ex
  lessons/
    lessons.ex
    lesson.ex
    note.ex
    transcript.ex
    video.ex
    attachment.ex
    lesson_attachment.ex

And it would be expected that lib/hello/courses/courses.ex and lib/hello/courses/lessons/lessons.ex are the public entry points since they have files that are named after the folders they are in.

As for naming conventions of the modules you could have Hello.Courses for lib/hello/courses/courses.ex but non-public modules would have the full path, such as Hello.Courses.Lessons.Lesson for lib/hello/courses/lessons/lesson.ex.

So my proposition is to potentially update the context documentation to include something like this, and if a strong enough use case can be made, maybe even change the generators to use this style by default. Chris said discussions like this belong here, so that’s why I posted this here.

This style is already being used on other projects successfully. Even the hexpm website uses something similar where files with a plural inside of a context are public, which you can see here: https://github.com/hexpm/hexpm/tree/master/lib/hexpm/accounts.

What does everyone think? Is there a better way to split contexts up so they work for both small and large contexts? What are you currently doing in your non-toy examples?

2 Likes
  • I’m not saying you are wrong.
  • I don’t have any prescriptive advice (when it comes to boundaries I think heuristics are the best we can hope for - from that point of view the “generators” are possibly doing more harm than good because they are setting the wrong type of expectations).

What irks me about the metrics that you are focusing on is that they don’t really deal with what I consider the core concern of contexts:

These capabilities may require the interchange of information — shared models — but I have seen too often that thinking about data leads to anemic, CRUD-based (create, read, update, delete) services. So ask first “What does this context do?”, and then “So what data does it need to do that?”

Sam Newman, Building Microservices (2015)

Repeat: What does this context do?

In your example all the module and file names are rather entity centric rather than capability centric.

I guess we have gotten used to names like Accounts - a better name would be UserAdministration because that is what the context does - the Accounts happen to be managed in the conduct of administrating users but for all we know there is more to it than that.

Similarly Course, etc. doesn’t really give me a good idea of what the context does or what it is responsible for.

Possibly once we are clearer about what does this context do it may become easier to determine whether 10 or a 100 functions are necessary for the context to do its job - or whether 100 functions are in fact a symptom that the context needs to be factored into multiple sub contexts that collaborate with one another so they can then expose their higher level capability through fewer functions.

1 Like

You are right in that they are very entity specific, but they all seem tightly coupled to that.

That single context would be responsible for:

  • Creating courses
  • Updating an existing course
  • Deleting a course
  • Bulk deleting courses
  • Listing courses
  • Showing a specific course
  • Creating a landing page
  • Editing a landing page
  • Updating a landing page
  • Deleting a landing page
  • Creating a package
  • Updating a package
  • Deleting a package
  • Bulk deleting multiple packages
  • Listing packages
  • Creating a section
  • Updating a section
  • Deleting a section
  • Reordering a section
  • Creating a lesson
  • Updating a lesson
  • Deleting a lesson
  • Reordering a lesson
  • Creating a note
  • Updating a note
  • Deleting a note
  • Getting a specific note
  • Creating a transcript
  • Updating a transcript
  • Deleting a transcript
  • Getting a specific transcript
  • Creating a video
  • Updating a video
  • Deleting a video
  • Getting a specific transcript
  • Creating an attachment
  • Listing all attachments
  • Updating an attachment
  • Deleting an attachment

Most of these would be “in the context of” a course. For instance, you would add sections to a course and then add lessons to those sections if you were an admin building a course. And a student would mostly watch courses where the sections and lessons are built into a table of contents, and then each lesson has a note, transcript, video and 0 or more attachments.

Basically, there’s a lot going on there that would only apply to admins (instructors) for assembling the course itself where as other side of the story is watching it.

Most of these things aren’t implemented yet, so that list is more of an off the top of my head list but there’s 40 functions there and I’m sure I would end up renaming them to be more friendly to my domain and I’m likely forgetting some stuff that would be added as well.

I don’t think it’s unreasonable to think there would be 50-75 functions in that single module which would be touching 10+ schemas.

That just seems like an awful lot of code to have in a single file and would make maintaining it pretty difficult. I feel like I would have to break up the module with comments by having things like # --- Sections so I could jump to specific points in the file, and that seems like for sure a design problem.

That single context would be responsible for:

I’m noticing a Create, Get, Update, Delete pattern.

Given that I would guess that your application may not have complex domain behaviour and therefore may not benefit from contexts. I suspect that most of your pages focus on a single table - if so you may be better off sticking to table modules.

There are circumstances where Smart UI is appropriate.

It is a common mistake to undertake a sophisticated design approach that the team isn’t committed to carrying all the way through. Another common, costly mistake is to build a complex infrastructure and use industrial strength tools for a project that doesn’t need them.

It’s pretty common to want to CRUD things in any application. I mean, you need to create a course, sections and lessons in order to have a “course”, and then you’re creating other associated things to those lessons.

This courses bit is only 1 of many contexts in this application. The whole platform has many other things which are listed in the original post:

I’m barely into creating this project but I already have contexts for accounts, checkout, payments, enrollments, courses and questions. I’m for sure going to have many more things too (progress tracking, affiliate systems, and the list goes on).

All of the other contexts are pretty tame. It’s just this courses one is out of control. But the 2nd problem is still quite prevalent.

It’s pretty common to want to CRUD things in any application.

I think the issue is that you are the developer and the domain expert and right now you are still using technical language rather than domain language to clarify the solution.

You are still primarily focused on ferrying data from the UI to persistent storage and back rather than defining your application in terms of the processes that your adminstrators and students have to enact and then defining the capabilities of your application that will support them in those processes.

Google “DDD vs CRUD” and have a look around. You’ll run into statements like:

Do use DDD when you know you’re modeling concepts and flows and forget that CRUD even exists.

CRUD vs DDD

You would prefer Check off the third entry! over UPDATE the third entry!.

What’s wrong with CRUD

Now some of those references will start talking about Domain Events and go down the CQRS rabbit hole - but that isn’t the point here.

I still believe that Phoenix Contexts are primarily about getting “business logic” out of the controller but beyond that context boundaries can be improved by focusing on “domain processes” rather than “domain data”.

but they all seem tightly coupled to that.

This is an area you may want to explore a bit. Naturally one might start out with:

Course [1] -- [0..*] Lesson

i.e. Lessons are tightly coupled to a Course largely because they need their Course to exist. A more complicated but less coupled schema could be:

Course [1] -- [0..*] CourseLesson [0..*] -- [1] Lesson

i.e. an associative entity CourseLesson is used to decouple the Course from the Lesson. Now Lesson could become an independent context while the Course context manages the Course and CourseLesson entities (while severely restricting the visibility of Lesson details - ideally it wouldn’t have any visibility but on a single DB you may want to allow details like “description” so that the Course context has just enough information to generate a lesson listing without having to coordinate with the Lesson context).

Whether or not this type of additional complexity is worth it depends entirely on the circumstances (it depends).

3 Likes

Surely the idea of contexts would be to group a domain model for a single database in one place - then if you wanted to reuse - you could shift to a lib and import.

It’s entirely possible that a system talks to more than one database - in that case use multiple contexts.

If the master module is long, is that a big deal? It’s all relatively simple functions, we’re not talking about OO spaghetti.

I’ve seen this before with umbrella apps (which I dislike), convoluted package structures, etc. Just because a feature exists doesn’t mean it has to be used as a core abstraction.

The context names that I picked so fall in line with a lot of examples I’ve seen online (even ones from Chris’ keynotes).

In this case, I don’t think the added complexity of a CourseLesson relationship is worth it for the sake of “boundaries”. For example, if a lesson became its own context instead of a nested folder (which I did have at one point in the first pass), I wouldn’t have a problem having it reference resources or call functions from a Courses context because I’m trying to be pragmatic about this.

At that point it really just becomes a way to “force” a second entry point and its primary focus is to split up a large file, since ultimately lessons are so related to a course.

It might be. I’ve worked on projects before with a well structured 1,500 line CSS file. Entire systems had to be put in place to keep it maintainable. Systems like things need to be ordered a certain way, comments with specific formatting, etc… It wasn’t fun and half the battle was just managing the file. Similar problems happen in fat Rails models too.

But on the flip side of things, if you go the other route and make very small contexts, then you’re basically back with the “models/” directory problem pre-contexts, except now it may be worse since you have potentially dozens of context entry points in your lib/hello/ directory, along with other files that aren’t even context entry points making it really hard to reason about your system.

Truthfully I’m almost ready to abort the idea of using contexts entirely and just go back to a schemas/ directory because really, I don’t think contexts help that much with breaking up your app for discover-ability since your entire UI (controllers, views and templates) are still a list of 1 massive flat unorganized directories.

You could still very easily keep business logic outside of your controllers without contexts.

For example 5 years ago when I started developing Flask applications, I used something similar to contexts to organize my code, except in that case, you would have folders like account, billing, contact, course, admin, etc. and inside of each folder contained your models, controllers and templates all wrapped together in a nice box for that specific blueprint (Flask’s term for labeling that type of folder). This made things quite maintainable, but Phoenix doesn’t really support this style of file organization.

How about just flatten your folder structure and split modules to multiple logical parts like this?

courses.ex
course_lessons.ex
courses/
  course.ex
course_lessons/
  lesson.ex
  node.ex
  transcript.ex

You could also use defdelegate in courses.ex and course_lessons.ex to forward function calls to inner modules and let courses.ex and course_lessons.ex be your public API.

More info here https://pragdave.me/blog/2017/07/13/decoupling-interface-and-implementation-in-elixir.html

You can always break those too, and it really helps to break them. If it’s a REST architecture, you should not have more than few actions per controller, preferably with dependent resources nested in subdirectories. And if it’s not REST… then you can still nest them. Nobody is forcing flat structure in the controllers, views and templates. To be truthful, nobody is forcing how should your directories and files structure look at all. If you want you can rid of controllers, views and templates altogether :slight_smile: If you want to organize your code in more Django way (because I believe Flask follows Django way of organizing code), than the Rails way, you really can do that. Look at the file contains the model, controller and view macros, see how elegant they are, and how you easily you can rewrite them, or just create your own (or just skip macros, and write code directly in modules) to support Django/Flask code structure.

For me one of the most powerful and least acknowledged feature of Phoenix is that it allows you to have your own code structure, with the smallest possible effort (unlike rails and it’s black magic).

2 Likes

That is an option but if code maintainability and glanceability are primary objectives isn’t this going to get really confusing because now once you go inside of that courses or course_lessons folder you’re not going to know which files are schemas and which files have implementations of what a public context file would do (except now it’s private and inside of your context’s folder).

I would love to.

Do you have any examples of what I would need to change in Phoenix so I can set up a directory structure that follows these rules:

  1. lib/hello_web goes away
  2. lib/hello/accounts is now a “real” context which contains this directory structure:
lib/hello/accounts
  controllers/
    user_controller.ex
  schemas/
    user.ex
  views/
    user_view.ex
  templates/
    user/
      ...
  channels/
    ...
  plugs/
    ...
router.ex
endpoint.ex
gettext.ex

And all of the helpers and everything else that Phoenix does under the hood would continue to work as is? In other words, the router and endpoint would still be a single file that lives somewhere outside of a specific context, and would apply to all contexts.

Because to me, a directory structure like this really promotes separating your domains. Now if you had 15 contexts and you want to work on “payments”, you know you just goto the “payments” folder and everything you need to work on that part of your app is there.

Where as the current context implementation is nothing like that. I still need to jump to 5 different spots in the code base and in the web folder I need to wade through 50+ controllers sitting in 1 directory, etc…

For reference the above directory structure is what I’ve been doing for ~5 years with Flask and it works wonderfully on large apps (I have some with 100+ models and dozens of controllers). It’s so easy to find things, and if I want to change a “context” name, it’s a matter of renaming 1 folder and doing a simple find / replace in my editor. Easy game.

Edit: For clarity the above directory style isn’t even officially supported by Flask or in its docs. It’s something I ended up doing out of need to manage a large project. It just so happens Flask doesn’t care how you organize your files (they can all be in 1 directory if you wanted). So it is possible to design a framework to be flexible with file organization. I didn’t have to fork Flask to make it work.

def controller do
    quote do
      use Phoenix.Controller, namespace: HelloWeb

      import Plug.Conn
      import HelloWeb.Gettext
      alias HelloWeb.Router.Helpers, as: Routes
    end
  end

  def view do
    quote do
      use Phoenix.View,
        root: "lib/app_web/templates",
        namespace: HelloWeb

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]

      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      import HelloWeb.ErrorHelpers
      import HelloWeb.Gettext
      alias HelloWeb.Router.Helpers, as: Routes
    end
  end

Look at the above macros you have in a root of xxx_web folder in file with your app name.

You either copy everything from controller part to your controller changing the namespace in relevant line: use Phoenix.Controller, namespace: HelloWeb to correct one, in your example Hello.Accounts or just create in some file a macro with that namespace changed, and use it in accounts controllers instead of the one in “root” of your application. Same go with the view macro, like above, copy it to each view module, or have it somewhere in some file in accounts directory like the controller macro. In view macro just change relevant lines

root: "lib/Hello_web/templates",
namespace: HelloWeb

to

root: "lib/hello/accounts/templates",
namespace: HelloWeb.Accounts

You have to experiment a bit I presume. Just have in every separate module, like the accounts, courses, billing etc. own set of macros that will set correct namespace and templates path to correct for that given module. In the router then you just put controller name as ususal

scope "/", Hello do
    pipe_through :browser

    get "/", Accounts.SomeController, :action
  end

If you want to have separate routers for each of submodule (accounts, courses, etc) then it’s just a bit more work, with maybe some more macros, but it shouldn’t be that hard. I simply never feel the need to do that. Having all the routes in one file worked great for me.

PS. I never worked large scale with Flask, but I did with Django, and being able to easily have everything where you want it, and not where framework authors tell you to (the horrible rails way) really do promote separation of concerns. Phoenix is really build in similar way. Those controller, and view macros are there for your convenience, but a) you don’t have to use them b) there are really smart, and allow you to easily modify them, so they can suit your particular needs.

And few last words about why context look like they do in Phoenix docs. So you won’t mix and match the presentation layer (controllers and views) with your business logic. What you have in Flask, and want to achieve with Phoenix is mixing those layers. For a long time I though that should not be mixed and kept separate, so you could just take business logic layer, and put it verbatim to another and just write another frontend, for example CLI app. But the problem is much more complicated, and there are no absolutes. So If you want have Flask like architecture of directories and files I help as much as I can, just be aware that you are separating whole “context” without separating business logic from presentation layer, and that may be not the best way.

While it’s clear that courses deal with lessons in some way the bigger question is whether “managing a course” and “managing a lesson” are very different activities from the user perspective. If they are different it’s a hint that we are dealing with separate contexts.

Your controller adjustments look promising. I thought I would have had to fork Phoenix to get it to work. Some of what you wrote went over my head. Do you think it would be possible to put together a minimal working example on Github showing this organization style?

You can still make that separation. For example, you’re not forced to put a ton of business logic in your controllers. You can put them in either your individual models, or create a Phoenix-like context file inside the folder.

Some of the large Flask apps that I have built also have a CLI component and I never had any issues reaching in and calling functions that I had defined in my models or other files. Even doing very business’y things like creating or syncing Stripe plans. The CLI commands themselves ended up being a few lines each that called functions tucked away in another place, and those functions could have also been called in a controller.

My Flask directory structure ended up looking like this (based on years of iterations while working on many projects as a freelancer):

# This is the root of the project.
assets/
cli/
config/
lib/
public/
hello/
  blueprints/
    account/
  migrations/
  tests/
  templates/

I really like it because:

  • assets/ had webpack related assets and static files (scss, es6 js, images, etc.)

  • cli/ had nothing but CLI specific code

  • config/ had various configuration settings for the app

  • lib/ had general purpose modules and functions I wrote for this project, but these functions aren’t specific to the project. In other words, stuff here could in theory be later extracted out to third party libraries and shared publicly for anyone to use.

  • public/ has the result of webpack parsing everything and this is what Flask / nginx would serve

  • hello/ had all of the app specific code broken up

    • blueprints/ had nothing but blueprints, so it’s super easy to glance what your domain is
    • Everything else had what you expect to be in there, the templates/ in this directory are for layouts and shared templates for all blueprints, since each blueprint would have its own templates

It really worked out nice on multiple large projects. Some of the biggest web applications I’ve ever built were with Flask, and some of them were handed off to companies where someone else took over the project once it was developed. People were always happy with the set up.

Now, half of that stuff is already in Phoenix, and there’s really nothing Flask specific about any of the above.

To me, what makes it good is you always know what you’re dealing with at a glance. Here’s 2 examples:

  1. If you go into a blueprints folder you know you’re dealing with some type of component of the application, and you can see everything you want to see about it all in 1 spot (the domain and the primary UI). You also know what things are inside based on what folders they live in or what file names they have.

  2. If you see stuff in the lib folder and find yourself copying it between projects, you know it’s prime pickings to potentially refactor it, write really good docs and open source it.

Phoenix just doesn’t have this at the moment. The lib/hello/* folder is a nightmare to glance because you have no idea what’s a general purpose library, something specific to your app that isn’t a context, something specific to Phoenix (like application.ex and repo.ex) or a context entry point.

From the end user’s POV (the student watching the course) they would always be viewing a lesson from within a course. A course is really just a big book that has sections and lessons. You pick the course you want to watch, and in a table of contents pick the lesson you want to watch. Then you’re presented with a video to watch about that lesson, and also have lesson-specific things to do (such as look at the transcripts, reference notes, attachments, etc.).

From the admin’s POV (the instructor assembling the course) you would typically create a course on its own with very little info (such as just its name) and then afterwards you would goto some type of interface that allows you to create sections and lessons, and then order them to build your table of contents.

But a course doesn’t just have sections and lessons + associated lesson “things”. There’s packages too (the thing you buy) and other things that I have listed out in the original post under the courses context. Those are also super important and related to courses, but aren’t so much related to the course watching experience (although packages are still important because the package you buy dictates if you have permission or not to watch a specific lesson).

@devonestes wrote a great article about this: http://devonestes.herokuapp.com/a-proposal-for-context-rules

At Novistore we’re using a macro to generate the ‘standard’ functions: https://gist.github.com/hl/c535b594b24e858d0e5ccfff29280c7a

Example

defmodule MyApp.Catalogs do
  use Context, repo: MyApp.Repo

  context MyApp.Catalogs.Product
  context MyApp.Catalogs.Collection, only: [create_collection: 1, delete_collection: 1]

  def list_products do
    # list all products
  end
end

defmodule MyApp.Sales do
  use Context, repo: MyApp.Repo

  context MyApp.Sales.Checkout
  context MyApp.Sales.Order, except: [delete_order: 1]

  def list_orders do
    # list all orders
  end
end
2 Likes

I read this last night and it was one of the only articles I could find online that tried to make sense of a non-trivial project, which I appreciated.

Although from the looks of things, it goes heavily against what Jose and Chris wanted for contexts because I recently found out my proposed solution in the original post was what contexts were back in Phoenix 1.3 but they decided to revert it.

Basically the idea of having directory and file names having a special meaning was frowned upon and in his article it seems like he takes the approach of files that end with plural form are secondary contexts and directories in plural form are where you hold schemas that are singular form.

Although, on that note, I really do like the idea of making a clean distinction between schemas, textbook / standard CRUD functions and “business logic that uses those CRUD functions which is what your UI uses”.

Do you happen to have any other usage examples of your gist? Potentially real examples where you exercise using all CRUD options (with and without optional preloads).

I have another suggestion for you: ditch the traditional Phoenix way and model your contexts as Use Cases:

You’ll have WAY more files, but each use case is self-contained and with just a glance over your folder you see what your context/system does.

It’s not in the post but for each context I hide the schema files in a __schema/ folder as they are just glues to the storage mechanism. This way you know that every file in the context structure is responsible for the business needs one way or another.

Another thing I would like to give my 2c is that “create course” is a perfectly valid name for a function name or a use case IF it is what you call it in your business. Forget about CRUD or programming in general and just think about your business:

You’re in the process of researching a topic and once finished you’re ready to launch the new course. How do you call it? If it’s in the line of “Hey, I just created a new course, you should check it out!” then create course is a perfectly valid name and it has nothing to do with CRUD.

And keep in mind that naming things is hard and sometimes you won’t get it right first, second or even third time. Heck, I work in the same domain (Intellectual Property) for 21 years and I still use create/update something because we couldn’t find the right terminology for the use case.

1 Like

Thanks for linking that.

This is a good snippet from the article:

There’s nothing technically wrong defining all these functions inside a single module in Elixir, but as contexts grows larger you start to have too many functions related to each other but loosely related to the parent concept. The cognitive overhead to build a mental state of all these functions is too much.

This is a common theme I’m finding in nearly every article I come across. It’s people struggling with having a million functions in 1 module, but Phoenix pushes you into having that single entry point file with everything in there with no documentation or insights on how to solve it.

You can know, or at least have an idea, what each context do just by looking at the file structure.

In your article’s example (cancelling an order) wouldn’t you end up with dozens or maybe even 100+ use cases sitting in your lib/hello/* folder, since those are the publicly accessible things that your controllers would use?

Yep it would work out to be that in this case.

But in an accounts context I have Accounts.sign_up_user(), Accounts.update_user_profile(), Accounts.update_user_account(), Accounts.get_by_user_email(), Accounts.change_user_profile() and other things that feel more “business’y” since that’s what’s really happening with those functions.

That accounts context is 200 lines long (with no documentation / minimal comments) and it’s super basic, only doing a few things like signing up users, allowing users to edit some settings and dealing with authentication mechanisms. The courses context above does SO much more, it will easily be 2,000+ lines of code but could truthfully end up being double that (I’m too early in the project to really gauge it).

The thing about CSS is that it manipulates a global state - a declaration at line 900 can completely change the behaviour of a declaration on line 20. With Elixir, and this is kinda what I was aluding to in my reference to OO, there is no global state - the worst thing you’ll have to do is hop, skip and jump through a couple of function calls.

1 Like

Fair enough, but it’s still going to be a problem. Think about a big Rails model.

You typically have:

  • Includes
  • Constants
  • Accessors
  • Relationships (has_many, etc.)
  • Validations
  • Maybe callbacks
  • Bunch of public methods doing whatever needs to be done
  • Bunch of private methods doing whatever needs to be done

You could easily end up with a 1,500+ line file here and almost a third of that could be validations alone in a scary enough model (such as a user model that got a little out of control in a 5 year old app).

So then you end up spending brain cycles on how to manage this file and you come up with rules like “things must be ordered in the exact way as above, ie. relationships always come before validations”. Before you know it, half your brain cycles are being used to remember these rules instead of focusing on the real problem.

This problem is amplified in Phoenix if you have a context responsible for a bunch of related things. Suddenly you need to remember to keep those 10 things all grouped up in the file, using exact naming conventions consistently across each one, and potentially ordering your CRUD functions in the same way (list, get, create, update, delete, change, etc.).

I don’t want to be in a position where I need to manage a 1,500+ line file like that. It would be madness. Then there’s creating good documentation on top of that (with potentially specs) and before you know it, you’re staring down a 5,000+ line file when you want to really just edit 1 little function about a lesson.