How to handle AR models associated with different components in a large monolith when breaking them apart?

This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.

Hey I’ve got a question: let’s say I’ve a big monolith and I want to break apart some components out of it. What happens if I end up with AR models belonging to different components but associated one to each other? How would you tackle that particular issue?

Message originally sent by slack user U78EKGG059M

We don’t allow AR models across boundaries - therefore we have broken associations (and foreign keys) and replaced them with service calls that return POROs (plain old ruby objects)

Message originally sent by slack user U78EVJFV25G

indeed - tim’s suggestion is good. i’d also consider turning those associations into API boundaries instead

but what if I need to have JOIN statements for tables (AR models) that belong to different components? You put the query on the component of the “root” model for the query?

Message originally sent by slack user U78EVJFV25G

maybe that part of the monolith should stick together?

Message originally sent by slack user U78EKGG059M

I agree with Robert - if you can’t cleanly separate them with an API - they probably aren’t truly independent concepts

Message originally sent by slack user U78EVJFV25G

and if they are… you will be spending some time refining your data model and inter-service comms situation

Message originally sent by slack user U78EVJFV25G

(grpc all the things) :slightly_smiling_face:

I agree with that, I’m just wondering if there might be cases where some models are needed on every single component, like Shop for Shopify or Post for discourse https://github.com/discourse/discourse/blob/master/app/models/post.rb

Message originally sent by slack user U78EVJFV25G

that feels to me (warning, gross generalization coming) like those things are ‘core’ kinds of concepts that should be codified into services easily accessible by all the other components

Message originally sent by slack user U78EVJFV25G

<@U78EKGG059M> i like how you think!

Message originally sent by slack user U78EKGG059M

Right back at you. And indeed at Root Insurance you can imagine a Policy is a pretty common concept used in all other components - but we return a PORO policy object that hides some of the internal complexity of a true Policy AR

Message originally sent by slack user U78EVJFV25G

nice, i like that

so, to confirm, you never run JOIN queries with the Policy table from components, right?

Message originally sent by slack user U78EKGG059M

that is a end goal - we aren’t there yet. but eventually all components will be isolated to their own DBs - and under test a component will only create its DB to enforce this rule

Message excluded from import.

Message excluded from import.

Message excluded from import.

Message excluded from import.

One reality is that you might have a table that just happens to exist in both components with the same name, even though different (even slightly).

This is really hard to see in a Rails monolith. And hard to code too without introducing coupling.

What Scott suggests brings you there, then you can end up with (piggy backing on previous examples) “Policy” in one component and “Policy” in another component, but they are indeed different tables (and they could be not in sync, if one system is slow) with different data.
The source of truth would be the published event, so the tables are just “read data” at that point.

It’s a long trip there, but it solves the coupling problem permanently.

With HTTP APIs and introducing API interactions through queries, what usually happen is having a database replicated at the HTTP layer with similar problems as before, plus the need for HTTP Authentication and dealing with downtimes of other systems