BLUF (Bottom Line Up Front): Before attempting to extract Rails Engines or microservices, you must map and enforce current domain boundaries. Shopify’s Packwerk uses static analysis to detect and prevent privacy and dependency violations between domains in a monolithic codebase. It allows you to gradually refactor without breaking the build.
Phase 1: The Dependency Spaghetti
In a large codebase, developers often bypass public interfaces and call private methods or constants across domain boundaries out of convenience.
Synthetic Engineering Context: The Packwerk Violation
After installing Packwerk and dividing your app/ directory into packages (e.g., packages/orders, packages/inventory), running the analyzer reveals the hidden technical debt.
# Terminal Output
$ bin/packwerk check
packages/orders/app/services/order_processor.rb:45:2
Dependency violation: ::Inventory::StockManager belongs to 'packages/inventory', but 'packages/orders' does not specify a dependency on 'packages/inventory'.
Privacy violation: ::Inventory::StockManager#decrement_stock belongs to 'packages/inventory', but it is not public API.
Phase 2: Implementing the Package Architecture
Packwerk requires you to explicitly define packages and their dependencies using package.yml files.
Execution: Defining Boundaries
Create a package configuration for the orders domain.
# packages/orders/package.yml
enforce_dependencies: true
enforce_privacy: true
dependencies:
- packages/shipping
If the orders package attempts to use a constant from the inventory package without listing it in the dependencies, Packwerk flags it.
Execution: Resolving Privacy Violations
To fix privacy violations, the inventory package must expose a deliberate public API. Packwerk considers anything in packages/inventory/app/public/ to be the public interface.
# packages/inventory/app/public/inventory_api.rb
module InventoryAPI
def self.reserve_items(sku, quantity)
# Internal logic hidden from the caller
Inventory::StockManager.new(sku).decrement(quantity)
end
end
Now, the orders package must call InventoryAPI.reserve_items instead of reaching directly into the internal implementation.
Phase 3: Next Steps & Risk Mitigation
Deploying Packwerk on a legacy app will initially generate thousands of violations. You must use the packwerk update-todo command to baseline the current errors into a package_todo.yml file, ensuring no new violations are introduced while you slowly refactor the old ones.
Need Help Stabilizing Your Legacy App? We use static analysis tools like Packwerk to map technical debt. Our team at USEO can help you establish strict domain boundaries in your legacy monolith.