What Comes After MVC

Via a Railsconf talk, What Comes After MVC by Peter Harkins.

Advice: most of these extractions can be applied partially, but the farther we go the better our code looks.

Goal: Split code based on two axes, mutability & side effects.

Immutable means: when we call methods on it with the same arguments, we get the same results.

No side effect means: when we call methods, no other objects change.

Value - immutable, no side effects

Extract values from ActiveRecord models to make them easier to reason about, and group similar behaviours. Don't have your values call or return ActiveRecord objects - they're implicitly mutable.

Consider overriding getter/setter methods for an attribute to auto-promote primitives (strings, ints, timestamps, etc) to value objects. Rails 5 attributes API helps here, but is a bit wordy.

Testing: - no let - no stub - no factory - no mocks - assert on results

Entity - mutable, no side effects

Extracting Entities from ActiveRecord: - Find identity (probably primary key). - Extract Values - Drive out side effects to Adapters & Shells.

Controvertial Opinion: ActiveRecord models shouldn't call their or other models queries (scopes, find, where) or lifecycle methods (create, save, reload) - eg. any methods with side effects.

Testing: - few lets for Values - maybe factories - maybe stub entites, but not Values - assert on results - assert on object state

Adapter - immutable, side effects

(named after Hexagonal pattern)

Testing: - few lets for Values - often stubs - asserts on mocks for outgoing queries/commands - asserts on results are probbaly not worth much

Shell - mutable, side effects

Testing: - fixtures with real-world data - might need factories to create enough Entities - expect on results, state, and mocks - Integration: one happy path to ensure objects glue together properly, and regression tests as necessary for confidence - if you have one integrated test, can stub Adapters later

Other - mutable, side effects

This is typical Rails code.

See also talks: - Boundaries by Gary Bernhardt - Magic Tricks of Testing by Sandi Metz - Integrated Tests are a Scam by JB Rainsberger - Domain Driven Design by Eric Evans

Closing Notes

Immutable objects cannot call mutable objects, effect-free code cannot call code with side effects. Thus:

The benefit of extracting Values, Entities, and Adapters out of the regular ball of code is so that we can have smaller pieces of code that are (a) easy to reason about, and (b) easy to test.

Empirical truth: once your tests start using ActiveRecord, they slow down immensely.