Need help with refactoring a nested monolith structure using `packwerk` or other methods

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

Message originally sent by slack user U71810IPUMS

My company’s monolith currently is a mess. I am trying to champion a refactor towards modularity; however, we have a fairly nested structure. What I mean by that is that we have pieces of business logic that would be great to put in chuck but that base on the type of product (and sometimes its subtype) the logic differs. As such we have app/services/complex_doer.rb that conditionally call app/services/product_type/complex_doer.rb which in turn may call app/services/project_type/subtype/complex_doer.rb . Can packwerk or something similar do nested structure and is there maybe a better way entire of doing this?

Message originally sent by slack user U71810IPUMS

Thanks for your time in advance

Message originally sent by slack user U71810IPUMS

It should be noted that the various complex_doers are calling out to command services and other thing, hence my desire to put them in packs. Especially as ideally, the application should only ever touch the top-level service

Message originally sent by slack user U717GMJTWHJ

You could start just by creating packs from the “big” concepts in the app and then potentially create child packs. And then you can create the public API surface for those so things aren’t reaching down into them… but I’d just start with what you think makes sense and then use packwerk to help figure out where the demand is for those APIs.

Message originally sent by slack user U71810IPUMS

So can packs have packs then?

Message originally sent by slack user U717GMJTWHJ

(Alex can confirm but I think nested packs are in the main packwerk gem now?)

Message originally sent by slack user U71810IPUMS

Well that makes things potentially much easier then

Packwerk does support nested packages, depending on what behavior you expect. Today, it doesn’t treat nested packages any different than any other pack. For example, packs/a and packs/b/ can both have a privacy/dependency violation on packs/a/c

But it is expected to understand that packs/a/c/path/to/file.rb lives in packs/a/c and packs/a/path/to/file.rb lives in packs/a

Message originally sent by slack user U71810IPUMS

Those sound doable. In my example, the goal would be to never call anything lower than packs/a and that packs/a/c would be private to everything except packs/a

Message originally sent by slack user U717GMJTWHJ

Packwerk can help you get there.

Message originally sent by slack user U717GMJTWHJ

  1. Create the packs
  2. See where the privacy violations are coming from and what they are.
  3. Build Public API to resolve them.
  4. Party :tada:

Message originally sent by slack user U71810IPUMS

Does it only support one layer (i.e. packs/a/c) or can it go deeper? In other words, could I have packs/complex_doer/product_type/subtype/path/to/file.rb?

Message originally sent by slack user U717GMJTWHJ

You can even start pretty coarse - though it’s easy to move things around.

Message originally sent by slack user U717GMJTWHJ

I wouldn’t recommend going deeper than one level.

Message originally sent by slack user U71810IPUMS

<@U717GMJTWHJ> That is the plan to start course

Message originally sent by slack user U717GMJTWHJ

More than one level of children feels … smelly … but I can’t say why I think that.

and that packs/a/c would be private to everything except packs/a

Packwerk doesn’t support enforcing this today. However – in packwerk 3, we are releasing extensible checkers. One of the first new checkers I’ll be releasing is a “visibility” checker. This means you’ll be able to specify explicitly that only packs/a can use packs/a/c, even if the other packs (e.g. packs/b) have listed packs/a/c as a dependency and are using public API.

Message originally sent by slack user U71810IPUMS

So if I understand this correct I would be going from something like

app/
└── services/
    ├── product_type_a/
    │   ├── sub_type_1/
    │   │   ├── complex_doer.rb
    │   │   └── assistant_to_the_doer.rb 
    │   ├── complex_doer.rb
    │   └── assistant_to_the_doer.rb
    ├── product_type_b/
    │   ├── sub_type_2/
    │   │   ├── complex_doer.rb
    │   │   └── assistant_to_the_doer.rb 
    │   ├── complex_doer.rb
    │   └── assistant_to_the_doer.rb
    └── complex_doer.rb

Message originally sent by slack user U71810IPUMS

to something like this:

packs/
└── complex_doer/
    ├── package.yml  
    ├── product_type_a/
    │   ├── package.yml
    │   ├── public/
    │   │   ├── complex_doer.rb
    │   │   └── assistant_to_the_doer.rb
    │   └── sub_type_1/
    │       ├── complex_doer.rb
    │       └── assistant_to_the_doer.rb
    ├── product_type_b/
    │   ├── package.yml
    │   ├── public/
    │   │   ├── complex_doer.rb
    │   │   └── assistant_to_the_doer.rb
    │   └── sub_type_2/
    │       ├── complex_doer.rb
    │       └── assistant_to_the_doer.rb
    └── public/
        └── complex_doer.rb