This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.
Hi all. In the context of gradually modularizing a monolith into packs… can anyone post some tips/links/recommendations on how to deal with the omnipresent User class? I’m aware of the technique of splitting a “Gob Object” into parts via a Facade. But even if we split User into different, more specific classes like EmployeeManager and Employee, ultimately we need all these object instances (and DB records) to represent a single person logging-in to our system. So each of these new smaller classes all end up depending on a (now smaller) System::User. Do you put this System::User in a quite general system package similar to rails_shims and then pretty much all other packages end up depending on it?
@stephan I couldn’t find an example for something like this in the 6/6 version of “Gradual Modularization for Ruby on Rails” book… but IMHO this is a very common problem your readers could face and it’d be useful to include something.
I’ve been splitting up a Company model with 408 methods, 51 columns, and 154 associations. I’m curious how much of my approach would be relevant for a User model.
Most of the stuff on the Company model are domain concepts that belong in different modules or packs. So I’ve been doing refactors to move methods from company or its concerns to domain packs. Our long term goal is a slim company model that is just identity information.
So I’m curious if there’s a path forward with a user class that only represents the user (id, email, tokens) and then if all the other concerns could be delegated to domain packs.
Hey @oboxodo! I wouldn’t call it system because that violates my personal rule of only using names that have the chance of me finding a boundary around them… (everything is a system…) That said, I have called this package Users. This package you want to have depend on almost nothing (probably rails_shims). The user class then should have only the methods than don’t fir on the employee, the manager, etc…
I once did the relationship between user and employee via inheritance… It was awful. I want to recommend looking for collaboration and composition patterns first
<@U70VMMV37TJ> yes, your Company sounds pretty much like my User. They’re “God” objects. What you’re describing is pretty much the same I’m planning to do.
So the question to you is: do each of these new classes inside the “domain packs” depend on the “slim” Company model you’re ending up with?
@stephan thanks for your reply. I agree about avoiding such a generic name as system. I’m not posting real names here, just basic names for the sake of examples.
So IIUC, you agree on having a bunch of “domain-specific user-like classes” in each different pack, then all of them having something along the lines of belongs_to :user, class_name: "Users::User" and that class would be a unifying glue with the main “id” for all those separate “aspects” of a user?
We don’t like sharing active records across pack boundaries, so I’m not sure I’d reach for belongs_to :user in a different pack. I’m curious if you’d be able to use user_id for the most part and hope there isn’t a foreign key problem. E.g. you’ll need to think about how destroy works so that you don’t have orphaned data.
yeah, I get your reluctance to sharing AR records across packs. But then for example… how do you get the “name” of a Company from one of your domain-specific objects representing the company? In my case, if I keep the “name” of the user in User, and I have Employee to represent the employee-data for the user… I have @employee.user_id but to get the name I’d have to either do @employee.user.name or User.find(@employee.user_id).name. Regardless of the choice, if that code is in the same pack as Employee (like an employees report) then I’ll have a dependency on User anyway.
For a pack to get the company name, it would need to load the company. The companys pack has a public API that has a value object for the slim company model. So a pack will call Companies.find(company_id:) and get the value object that has the name in it.
I’m coming to this thread late but I wanted to add to the discussion since it has been something I’ve been dealing with lately.
I’ve been following a similar approach to U70VMMV37TJ where I’m taking a lot of existing code and removing the dependency between the god model and the package if it uses the god model as an identifier\tenant. For cases where the god model is actually used, then I think it is fine to have a dependency on it where the package accesses what it needs.
Some related resources that you might find interesting: