Skip to content

Instantly share code, notes, and snippets.

@mediafinger
Last active October 25, 2022 23:30
Show Gist options
  • Save mediafinger/e586fe12becd4533cddbdf0e24a28588 to your computer and use it in GitHub Desktop.
Save mediafinger/e586fe12becd4533cddbdf0e24a28588 to your computer and use it in GitHub Desktop.
Modularizing a Rails Monolith with Packwerk - collection of ideas and resources

Packwerk

Is a Ruby gem to help separate concerns inside a Rails monolith.
https://github.com/Shopify/packwerk

Treat data modeling for packages like data modeling for network-isolated services:
hold foreign IDs referencing models across package boundaries, but do not hold strictly enforced foreign database keys and do not use ORM associations that would hide the separation of concerns.

Following the upper advise would be quite a change for our models. But less dependent: :destroy | :nullify | nil decisions could actually make our lifes easier and our data more consistent (or just more fault tolerant). And not to forget that Packwerk allows to start small and increase its checks file by file.

Packages

Move code with high cohesion into packages and then enforce boundaries by defining permissions and restrictions and a failing linter on the CI.

A package should contain an app/ folder with all the typical and custom subfolders like controllers/, models/, services/ and so on and a spec/ folder with all the model/, request/ and other specs relevant to this packages.

Let's keep the following folders on the root level of our Rails app:

  • lib/ folder with all our app specific code, that is neither business logic nor managed by Rails
  • /db folder, as we will keep using the same database (possibly we'll introduce namespaces to tables)
  • /config folder as it contains general configuration for all the app

Possible new structure:

Creating a package just means to:

  • copy the code into a new folder app/packages/package-name/
  • adding a packwerk.yml file to this folder, configuring the boundaries

Every bullet point with items under it, should be a package - this means nesting packages.

  • organization (for now propably just keep code in the "base" / "main" app package)

    • company
    • project
    • user
  • branch

    • routing
      • route management
      • internal
        • traveling-salesman
        • mapbox
      • wdl
        • sync-services
        • route generation
  • industry

    • tickets
      • order
      • billing
    • plant
      • site
      • area
      • zone
  • resources

    • container
    • device (or does this belong into routing?)
    • location (or use more specific name like: garage / dumping_station)
    • vehicle
  • emptying planning

    • emptying templates (or do they belong to container or routing?)
    • sensors and fill_level data (or do they belong to container or routing?)

First steps

Start with just one package - or only a part of a package - copy all the files belonging together (high cohesion) into a new package (==subfolder) and add the packwerk config files to this folder.

Don't update the code or naming just yet - only define the boundaries (which files / packages are allowed to access anything in this package && which files / packages can be access from inside this package) and generate an allow-list by the violations that will be generated.

Only now start introducing code changes to establish clear boundaries (like an API) between packages.

Later steps

Enforce the boundaries by defining them more strictly, reduce the exceptions and let the CI fail in case of violations. This should entail that no package can directly access a model class in a another package or receive an instance of a model. Use serialized objects like in an API and use service / api classes if you need to trigger a CRUD operation.

I would also recommend to move all the code into a package namespace (including its database tables), but this might be easier todo, once most calls are done inside a package with clearly defined exceptions.

More resources

As a bonus a book in the making: Gradual modularization for Ruby and Rails:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment