Personalised dog nutrition, sold by subscription.

The product is fresh-cooked, recipe-tailored dog food: each dog has a profile (weight, age, breed) and gets meals matched to it, ranging from fresh-cooked entrées to organic treats and supplements. Cooked in a USDA kitchen, flash-frozen, delivered on the customer's schedule. Distributed both directly to the customer and through the independent pet retail channel.

The brand was already past the early Shark-Tank-era days of a single founder cooking in Brooklyn and hand-delivering meals around New York. By the time we joined, the operation was running at thousands of active subscriptions across the country — the kind of scale where the engineering, not the recipe, is the bottleneck.

The platform was on a Rails monolith with a Next.js storefront and an Active Admin back-office. It worked. But it had grown organically for years, and the cracks were starting to show in the places that matter for subscription commerce — payments, billing edge cases, customer-service tooling, the speed at which marketing could ship a campaign.

What we walked into.

The first sprint inside any inherited codebase is always a survey. Ours surfaced the usual signals plus a few that aren't usual:

  • Naming drift between the data layer and the domain model. The plans table was modelled as BoxVariant; box_plans_skus was MealVariant; products was Addon; the actual products lived in dishes and were called Meal in the model. Two parallel Order and Payment hierarchies (Order vs V2::Order) told the story.
  • Methods calling methods calling methods. Following the path of any non-trivial transaction was a debugger session, not a read.
  • 54% test coverage that was less than it looked. Many tests were redundant, some were testing the wrong invariants. A realistic floor was somewhere between 30% and 40%.
  • An Active Admin back-office that had been extended past where Active Admin politely accepts extension.
  • A circular dependency or two in the data model — the kind that survives small refactors and breaks the big ones.
  • One recent migration disaster: a brief move off Stripe to MerchantWare, where payment failures roughly tripled and the migration was reverted within months. Stripe stays. We learned the lesson and moved on.

Inside this perimeter, the platform was still serving thousands of subscribers every week without missing a billing cycle. The job wasn't to throw it out — it was to refactor it in flight, while shipping every other week.

A subscription stack, refactored mid-flight.

The product runs on Rails (API + admin) with a Next.js storefront, hosted across Heroku and Vercel. A component-based engine layout cleanly separates payments, subscriptions, accounts and shipments — the result of a multi-step refactor we ran while the platform kept shipping every week.

Ruby on Rails API · Engines
Next.js Storefront
PostgreSQL Database
Sidekiq Background jobs
Elasticsearch Search
Redis Cache
Heroku Hosting · API
Vercel Hosting · Web

Component-Based Rails Architecture, in five steps.

The plan we ran was a multi-phase refactor toward a component-based Rails architecture — modular Rails engines per business domain, each isolated enough to be tested and reasoned about independently, but still living inside one deployable backend.

5phases From a coupled monolith to engines for payments, subscriptions, accounts, shipments and more.

The phases, briefly:

  • Phase 0 — survey. Map the existing system, dependencies, the integration surface (Klaviyo, Yotpo, ShipStation, AfterShip, UnDigital), and the shape of the day-to-day operational pain.
  • Phase 1 — namespace into an engine. Pull modulation out of the main app and put it behind an engine boundary, without changing behaviour.
  • Phase 2 — isolate engines by business logic. Carve out engines per domain: PaymentEngine, SubscriptionEngine, AccountEngine, plus shipments_by, events_by, settings_by, coupons, common_by. Each engine owns its slice of the data model, exposes a clear API, and can be tested in isolation.
  • Phase 3 — CI/CD that scales with the team. Parallelised tests, unit and integration coverage rebuilt from scratch, code documentation, materialised-view review on the database side, and a single Dockerised environment across local, review apps and production.
  • Phase 4 — refactor and follow the patterns. Stop fighting Rails. Apply the conventional patterns, retire the workarounds the legacy code had grown around them.

None of this happened on a feature freeze. The team kept shipping subscription features, marketing campaigns and back-office QoL improvements through every phase. Refactor in flight, every release green.

How a sprint ran.

Methodology was straight Scrum: a fortnightly planning-poker session, a fortnightly sprint planning, daily standups, sprint retrospective. Tasks lived in Asana with a custom workflow: To do → In progress → Code review → QA → Ready to deploy → Done, with an explicit Blocked state for anything waiting on the client side.

Branching is what the team ended up calling Git Sub Flow — neither full Git Flow (too much overhead for a fast-shipping product) nor pure GitHub Flow (too lean for a subscription platform that couldn't afford a bad release). Features and hotfixes branched off master, shipped through review apps, got code-reviewed, QA'd, and merged. Production deploys were tagged. Hotfixes had their own narrow path back.

Day-to-day, the work split across three buckets: quality-of-life features for marketing, customer support and admins (CX-driven payment-method updates, holiday-impact alerts on orders, admin panels, data filters); bug fixes on the payment, order and subscription paths; and the refactor running underneath.

Seven years on PetPlate.

Runtime was on PetPlate from 2018 through 2025 — through the early consumer-scaling years and the inflection where the product crossed from "running" to "needs serious engineering inside it". The core team that delivered the architecture refactor came together through the second team setup: two Runtimers in September 2021, growing to eight by mid-2022, full-stack engineers covering Rails and Next.js plus a dedicated QA specialist and two team-leader rotations.

Across the engagement the codebase moved from a coupled monolith to a component-based engine layout. Payments and subscriptions were separated from the legacy core, the back-office gained the quality-of-life features the operations team had been asking for, and the test suite reflected real coverage. Late in the engagement, focus had shifted to combo plans — a product expansion mixing fresh-cooked entrées with kibble for customers blending wet and dry food.

This is the shape of engagement Runtime keeps coming back to: senior engineers embedded inside an operating product, owning architecture decisions alongside the in-house team, refactoring in flight, shipping every week.