In the presentation, Turn old into new: Moving to elixir feature by feature, Anita Ludermann walked through how her team is migrating a large, revenue-critical product from a legacy Perl/Foswiki codebase to Elixir — without freezing feature delivery or breaking customer workflows. The core theme: incrementalism beats big-bang rewrites.
Turn Old into New: Moving to Elixir Feature-by-Feature: A comprehensive overview
Context: Product, team, and constraints
- Company & domain: Quality-management software (SaaS-first, some on-prem), auditing features, collaborative process documentation.
- Team ways of working: Test-driven development, CI/CD (fast rollouts and rollbacks), Shape Up cycles (roughly two-month enterprise releases), and Domain-Driven Design (DDD).
- Reality check: They must keep shipping. Customers won’t wait years for a rewrite.
Why move at all?
- Aging stack: Frontend (Vue) fine; database (Postgres) fine; backend (Perl + Foswiki fork) was the problem. Docs are stale, modern capabilities missing (e.g., websockets), community quiet.
- Legacy dynamics: Years of “startup hustle” customization led to spaghetti code, “black magic,” little test coverage, and low modularity — making changes risky and slow.
Take: “Appreciate the legacy” — it got you here — but design a path out that preserves continuity.
North star: DDD + Elixir
- Vision: Carve the “big ball of mud” into bounded contexts; isolate features; reduce coupling.
- Language choice: They wanted FP either way; Elixir won over alternatives because of ecosystem fit, approachable docs, friendly community, and team familiarity.
- Scope discipline: Start with the backend; consider LiveView later.
Step 1 — The “walking skeleton”
Goal: ship a vertical slice to production that nobody notices.
- Seamless UI
- Keep Vue; add Elixir services behind existing REST patterns.
- Single sign-on/session continuity
- Custom Phoenix plug that validates the browser cookie against the existing Perl/Foswiki session store, returning user/tenant/roles (RBAC).
- No second login.
- Data consistency & multi-tenancy
- One Postgres, per-tenant schemas.
- Migrations for public (global) and tenant schemas; always idempotent with up & down paths.
- Provisioning/deprovisioning extended to include Elixir services.
- Used Triplex for schema-per-tenant strategy (and kept some migration work in Perl early on to move fast).
Outcome: In ~2 weeks they had Elixir in production; in ~6 weeks the first feature shipped. “Not pretty,” but viable — and confidence-building.
Cross-context communication without re-entangling
Synchronous calls across Perl↔Elixir would add latency and coupling. The team leaned toward events:
- Initial pragmatism: Publish to the existing event queue in the shared database (quick and ugly, but it worked).
- Listening from Elixir: Database triggers nudged Elixir consumers.
- Direction of travel: Stand up a native Elixir event queue as Elixir owns more core flows.
Principle: Prefer event-driven boundaries; accept eventual consistency where appropriate.
The big example — Attachments via the Strangler Fig Pattern
Attachments were a large, core legacy feature. The team moved it in small, safe slices:
- Start tiny: Implement only what was needed for profile pictures in a new Elixir “attachment service” (save/load metadata; read/write file storage).
- Read path first: Expand to read metadata for all attachment types through Elixir; keep writes in Perl.
- Then write path: Move writes to Elixir and remove Perl’s DB/file-storage code for attachments.
- Flip the API: Point the frontend REST endpoint at Elixir; delete the remaining Perl attachment service paths.
Net effect: A major feature migrated with no visible user disruption, significant Perl deletion, and clearer ownership. It took months (amid other work and an S3 move), but it stuck.
Where they are now
- Codebase trend: Perl still substantial, but Elixir lines are steadily growing — and Elixir implementations are much smaller for the same feature set.
- Performance: Users perceive Elixir parts as faster (less bloat, cleaner flows).
- Hiring & learning: Elixir proved easier for newcomers and enticing for frontend devs moving full-stack.
- Cadence: Not as fast as the earliest hopes (business priorities + discovery of hidden legacy “features”), but momentum builds with each migrated context.
Ongoing challenges
- New features vs. modernization: Permanent balancing act.
- Unearthing implicit behavior: Highly customized legacy means users rely on undocumented quirks. Migration often surfaces those — and requires product decisions.
Practical takeaways you can reuse
- Incremental steps all the way down. Avoid rewrites; ship vertical slices that exercise auth, data, and UX.
- Set a guiding architecture. DDD boundaries + event-driven integration prevent a new monolith of tangled code.
- Own migrations. Multi-tenancy, idempotent scripts, up/down always, and CI-friendly rollout strategy.
- Honor sessions. Reuse the existing session store at first; swap later. Users must not notice the seam.
- Prefer reads-first migrations. Flip read paths to the new service before writes; then flip the API.
- Be pragmatic about tooling. Early on, keep “what already works” (even in the old stack) to de-risk.
- Measure progress by deleted legacy code and isolated contexts, not by “100% rewrite.”
- Invest in tests and quick feedback loops so tiny moves stay safe and reversible.
Recommended reading from the talk
- Working Effectively with Legacy Code — Michael Feathers
- Refactoring — Martin Fowler
- Kill It with Fire — Marianne Bellotti
Final word
Anita’s team shows that you can modernize in motion: keep shipping, keep learning, keep carving off features. Elixir’s developer ergonomics, pattern matching, and community were force multipliers — but the real secret was the method: small slices, clear boundaries, and relentless reduction of legacy surface area.