Converging on One: The Business Case, the Scar Tissue, and the Strangler Fig

Executive28 min readPost 7 of 10

Four higher-education courseware platforms, four technology stacks, and four teams that had each solved the same handful of problems in a different way. Between them the portfolio generated close to $750M a year, with one platform alone accounting for roughly $400M of that. None of the four could be touched without risking real revenue, real enrollment cycles, and a seasonal peak that made a good part of the year off-limits for anything risky. This is the story of how I led that convergence end to end: personally auditing the entire estate before any plan reached anyone else, building the business case after an earlier attempt had already failed in an expensive and instructive way, winning the funding to do it properly, and then directing execution so the business never had to stop for it.

A note on names before any of this: the technology, the numbers, and the sequence of events below are real. The platform names are not. I have swapped in stand-ins so the piece can stay focused on the pattern rather than on trademarks.

The portfolio, and what fragmentation was actually costing us

The portfolio had grown the way most large portfolios grow: not by design, but through a decade of acquisitions and expanding product lines, each arriving with its own engineering team and its own opinions about how a course, an assignment, or a gradebook ought to work. By the time I stepped into the role, four of these engines were running in production. Amplus was the anchor: a .NET monolith with a 6.5 million user base, 1.4 million peak daily active users, 35,000 concurrent users at peak, coverage across 49 academic disciplines, and just over $400M a year in revenue on its own, the single largest product in the portfolio. Magistra ran on Perl services, C++ rendering binaries, and an Oracle RAC cluster. Griffon had already been partially rebuilt into a set of .NET Core services sitting behind an API gateway. Verve ran on a Node.js and Scala hybrid, a cluster of specialized services handling assignments, engagement, and delivery that communicated with each other over a message bus. Combined, the four generated close to $750M in annual revenue and served millions of students and instructors across the higher-education market.

None of that was a failure of engineering judgment. Each platform had solved a real problem for a real set of customers at the moment it was built. What it added up to, four platforms later, was an expensive global problem sitting on top of four reasonable local decisions: four gradebook implementations, four assignment engines, four ways of integrating with a learning management system, and four separate identity and security models to maintain. Academic partners who wanted to integrate once had to integrate four times. Engineers who wanted to ship one feature had to design, build, and test it four times, once per stack, often against four subtly different specifications. We were paying for the same capability more than once in nearly every category that mattered, and none of the four platforms carried enough shared infrastructure underneath it to make adding a fifth capability, generative AI being the obvious one on the horizon, anything less than a four-times effort as well.

Students and instructors Amplus .NET monolith 26TB unsharded SQL 3,889 stored procs about $400M / yr Magistra Perl + C++ render Oracle RAC Griffon .NET Core services behind an API gateway Verve Node.js + Scala message bus services Four gradebook implementations. Four assignment engines. Four identity models. Four LMS integration paths for the same academic partners. Nothing built once.
The portfolio before convergence: four platforms, four stacks, four of nearly everything a student or instructor touched.

Reading the entire stack before proposing anything

Before any transformation plan reached the executive team, I made a deliberate call to spend the first month on the ground myself: an architecture and data audit across all four platforms, not a summary handed up by four different teams, but a first-hand pass through the code, the schemas, and the operational runbooks of each one. A business case for a $750M portfolio only holds up on numbers the person presenting it can defend without notes, not secondhand impressions relayed from four teams with four different incentives.

Amplus's numbers were the most alarming and, later, the most persuasive. A single, unpartitioned, unsharded SQL Server instance sized at 26TB, of which 17TB was active transactional data, ran a library of 3,889 stored procedures against roughly 1.3 million lines of application code. Core business logic, not just data access, lived inside those procedures, which meant every deployment carried the risk of a change nobody could fully trace end to end, and every attempt to scale horizontally ran straight into a database that could only scale up, never out. Magistra's Perl, C++, and Oracle RAC combination was stable but increasingly difficult to hire for and slow to extend; the rendering layer in particular had grown tightly coupled to a specific version of its runtime. Griffon had already been through a partial internal modernization onto .NET Core services with its own gateway, which put it ahead of the other three technically but still fully isolated from them: nothing it learned about a student was visible anywhere else. Verve's message-bus architecture was sound in isolation, a reasonable design for its own domain, but it spoke a data model that none of the other three platforms could read.

The audit did two things. It surfaced, in specific and defensible numbers, exactly where the risk actually sat, and it gave the proposal a common vocabulary the executive team could not wave away. Leading with the fact that the anchor platform alone carried $400M of the portfolio's $750M in revenue, on top of a stored-procedure library that had outgrown anyone's full understanding of it, turned the conversation away from technology preference and toward risk the business was already carrying, whether anyone had chosen to or not.

The scar tissue: why the obvious answer had already failed once

The instinct, once those numbers are in front of you, is to propose the clean version: retire all four, build one new platform from a blank page, done. That instinct had already been tried a few years before I arrived, and working around its aftermath turned out to be the single biggest obstacle in front of me. The organization had recognized the same fragmentation and contracted an external vendor to build a full replacement platform from the ground up. The engagement ran for the better part of two years and consumed a meaningful multiple of what a disciplined convergence would eventually cost. What came out the other end could not support the academic domain's actual functional requirements, buckled under load well below what a single one of the four existing platforms already handled at peak, and had accumulated what everyone on the inside called, without much argument, a spaghetti architecture: no clear domain boundaries, no consistent data model, business logic scattered across layers that were never designed to hold it. The initiative was quietly shelved.

That history mattered more to my funding conversation than any architecture diagram did. It meant the word "rewrite" was functionally radioactive in the room, and any plan that could be mistaken for another attempt at one was going to be a hard sell regardless of its actual merits. Leadership had learned, correctly, to distrust proposals that asked them to bet the business on a platform that did not exist yet. Kotter's work on leading change describes exactly this dynamic: an organization's memory of a failed transformation becomes an active defense against the next one, not because people are being unreasonable, but because they paid for the lesson once already. Any credible plan had to be visibly and structurally different from what had already failed, not just better-intentioned than it.

Building the business case, and the funding conversation

So the plan I brought back was not a rewrite. It was a convergence, anchored on the platform we already trusted with most of the revenue. Instead of building a fifth platform and asking four teams and their customers to migrate onto it, we would transform Amplus from the inside, using its existing operational footprint, its existing customer base, and its existing uptime record as the foundation, and absorb Magistra's, Griffon's, and Verve's functionality into it one domain at a time. Nothing new had to earn trust from zero. The platform doing the absorbing was the one already carrying $400M a year without incident.

Winning the board over took three arguments, and I made sure each one was framed in the language the board already used to make decisions, not the language I would have reached for as an architect. The first was revenue protection: the riskiest migration work would happen behind feature flags and canary releases, with the ability to revert instantly if an error-rate threshold was crossed, specifically so that nothing touching the highest-traffic paths would be exposed during the Back-to-School enrollment surge the entire portfolio's revenue depended on. The second was consolidation economics: four separate engineering and operations footprints collapsing into domain-aligned squads meant building a capability once and integrating it once, instead of building and maintaining it four times, with a direct reduction in cloud spend, licensing, and duplicated headcount. The third was optionality on AI: a unified API and data layer meant we could introduce generative AI capabilities as part of the modernization itself, directly into the converged platform, instead of retrofitting four different integration points for it later, at four times the cost, after the technology had already moved on.

That third argument carried more weight than anticipated. Generative AI was not yet in production anywhere in the portfolio, and the prospect of building it once into a platform designed to receive it, rather than bolting it onto four legacy stacks in sequence, moved the funding conversation from a defensive one about avoiding another failure to an offensive one about a capability the business did not yet have. The board approved full funding for what we named, together, the Convergence Program.

Executing the convergence

Anchoring on one platform, not building a fifth

With funding secured, I set one non-negotiable rule for the program before a single line of the new architecture got drawn: there would be no fifth platform, ever, at any point in the plan. That rule was what made the whole program credible to teams who had watched the last attempt turn into exactly that. Amplus would absorb the other three, domain by domain, behind a set of domain-driven microservices, until it stopped being "the monolith with three neighbors" and became the single application the portfolio had never had. Domain-driven design gave the team the vocabulary for the boundaries themselves: course, assignment, assessment, and grading each became a bounded context with its own model and its own ubiquitous language, instead of one shared, tangled schema that every team had a different mental model of.

Getting to those four boundaries was its own exercise, not a diagram drawn in an afternoon. Each of the four platforms had its own working definition of what an assignment was, what counted as a course, and where grading logic actually lived, accreted independently over a decade with no shared vocabulary between the teams that built them. We ran a series of facilitated domain-modeling workshops across all four engineering teams, close to what the industry calls event storming: naming every concept in its own ubiquitous language, then mapping where those languages actually agreed and where they only appeared to. Assignment in Amplus and assignment in Verve turned out to share a name and almost nothing else underneath it. That exercise, not the technology, is what produced the four bounded contexts the rest of the architecture was built around, and a proper domain-driven design context map, shared kernel here, conformist there, anti-corruption layer everywhere else, is what kept a well-intentioned team from drawing service boundaries that looked clean on a whiteboard but did not match how any of the four businesses actually worked.

The payoff of getting those boundaries right shows up as a straightforward complexity argument. Four platforms each integrating separately with the same academic partners and the same learning management systems is a point-to-point integration problem, and point-to-point integration is well understood to scale roughly with the square of the number of systems involved rather than with the count itself: four systems integrating directly can mean as many as a dozen distinct paths, each with its own data shape and its own failure mode. Converge the same four platforms behind one set of domain services and one integration contract, and the same academic partner integrates once, through one path, in one shape. The architecture did not just get more modern. It got structurally simpler, and that reduction in integration surface was as much a part of the pitch to the board as any efficiency number.

BEFORE: POINT TO POINT Amplus own stack Magistra own stack Griffon own stack Verve own stack Academic partners and LMS four paths, four data shapes AFTER: ONE CONTRACT Domain services Course, Assignment, Assessment, Grading one bounded context each, one ubiquitous language, one contract out Academic partners and LMS one path, one data shape
Before, four platforms each integrated with academic partners on their own terms, a point-to-point pattern where the number of paths grows faster than the number of platforms. After, one set of domain services behind one contract replaces all four.

Matching the old schema before building the new one

The instinct once a bounded context is defined is to give it the schema it deserves: clean, normalized, free of the compromises the legacy database had accumulated over a decade. We deliberately did not do that first. Every new domain service launched with a data model that mirrored Amplus's existing contracts field for field, warts included, because a microservice that already agrees with the monolith is one the strangler facade can route to blind, and one whose output can be diffed automatically against the legacy path on every single request during the transition. This is the anti-corruption layer pattern from domain-driven design, applied in reverse for a period: instead of the legacy system corrupting the new domain model at the boundary, the new domain model spoke the legacy dialect on purpose, for exactly as long as it took to earn trust.

That sequencing decoupled two risks that most failed convergence efforts take on at the same time: migrating the data and redesigning the schema. Do both in a single change, and a production incident could be either one, or both, with no fast way to tell which. Do them in sequence, matching the old schema first and validating with real production traffic that the new service reproduces the old behavior exactly, and a discrepancy can only mean one thing. Only after a domain had been running on the new service in production for a full cycle, meaning both a normal and a peak-load period, did we start what the industry calls an expand-contract migration: adding the cleaner fields the bounded context actually wanted, dual-writing both the old and the new shapes, backfilling history, and only then dropping the legacy-shaped fields nobody was reading anymore. The schema earned the right to be redesigned by first proving it could be trusted.

The strangler fig, phase by phase

The migration pattern itself was Martin Fowler's strangler fig application: grow the new system up around the edges of the old one until the old one can be quietly removed, rather than attempting a single cutover that puts the entire revenue base at risk on one release night. We ran it as five phases.

  1. Phase 0, synchronize the domain models. Before routing a single request differently, we aligned the new domain services' data models with Amplus's existing contracts, so the new services could initially just mirror the old behavior rather than reinvent it. This is where a surprising amount of the real risk got retired early, quietly, before any user-facing change existed to blame it on.
  2. Phase 1, introduce a facade. A facade layer went into the .NET application itself, splitting course-related and assignment-related calls so each could eventually be routed independently, either to the legacy stored-procedure path or to the new domain service, without the caller needing to know which.
  3. Phase 2, dual-write. Once the facade could route, we turned on writing to both the legacy SQL Server tables and the new purpose-built stores at the same time, with reconciliation jobs running out-of-band to catch drift between the two. This is the phase where you find out how good your idempotency story actually is, and ours was not good enough on the first pass; more on that below.
  4. Phase 3, domain single-page applications behind a backend for frontend. Each domain got its own React micro-frontend and its own backend-for-frontend, consuming the new domain services through GraphQL while the data underneath still round-tripped back to keep the legacy path in sync.
  5. Phase 4, canary routing to the end state. Feature flags moved real traffic gradually, first to test cohorts, then to a percentage of production, then to everyone, with an automated rollback if error rates crossed a threshold. Once every domain reached one hundred percent on the modern path, the legacy facade for that domain came out.
USER REQUEST Student or instructor FEATURE FLAG Traffic router canary, percent split LEGACY PATH Amplus facade .NET and stored procs primary for routed users MODERN PATH Domain SPA + BFF GraphQL, domain services test users first, then all DUAL SYNC Reconciliation and conflict resolution idempotent keys, out-of-band jobs
The strangler fig in flight: a feature flag decides who sees the legacy facade versus the new domain path, while a dual-sync process keeps the old SQL Server instance and the new domain stores in agreement.

Decoupling a 26TB monolith

The data layer was where the strangler pattern earned its keep. We carved domain entities out of the 26TB SQL Server instance one at a time and moved each into a store chosen for how it was actually read and written, an approach the industry calls polyglot persistence rather than one shared database asked to be good at everything. Course and assignment state, which needed fast key-value reads at very high concurrency, went to DynamoDB. The relational core, the parts that genuinely benefited from joins and transactional boundaries, went to a managed PostgreSQL instance. Prerequisite mapping and content relationships across learning paths, which are fundamentally a graph problem dressed up as a relational one, went to Neptune. Each choice followed directly from the bounded context's own access pattern rather than a house standard, the same reasoning behind command-query responsibility segregation: a service's write model and its read model do not have to be the same shape, they only have to agree, and a data store chosen for how a domain is actually queried will outperform a data store chosen for consistency with its three neighbors every time.

Before touching a single stored procedure, we ran runtime telemetry across the database cluster for a full academic quarter to trace actual call paths, parameter distributions, and execution frequency, because the written documentation for a decade-old library of 3,889 procedures was, unsurprisingly, wrong in enough places to be dangerous on its own. That forensic pass, closer to reverse engineering than to reading a spec, is what let us reimplement the logic that mattered inside the new domain services with confidence, and quietly retire the logic that hadn't actually been exercised in years.

PRESENTATION React micro-frontends: Course, Assignment, Assessment, Grading, Admin API GATEWAY + GRAPHQL BFF Auth, rate limiting, one contract per experience DOMAIN MICROSERVICES Course Assignment Assessment Grading AI ENGINE Study assistant (content model) Instructor agent (ReAct, tool use) Guardrails: content filtering, PII redaction POLYGLOT PERSISTENCE DynamoDB (course and assignment state), PostgreSQL (relational core), Neptune (prerequisite graph)
The converged target: micro-frontends over a single GraphQL backend for frontend, domain microservices sitting beside the AI engine as a peer, and purpose-built stores chosen per access pattern instead of one shared database asked to do everything.

Team topology follows the architecture

Architecture and org chart move together or they fight each other, and I had learned that lesson the hard way on an earlier program. Conway's Law says a system ends up mirroring the communication structure of the organization that builds it; the inverse Conway maneuver says you can use that on purpose, by shaping the teams you want the architecture to end up looking like. We moved engineering through three stages: a historical "sustain" model with siloed development and operations, a transitional "optimize" stage that centralized platform templating and monitoring, and a target "grow" model built around two-pizza, domain-aligned squads mapped one-to-one onto the bounded contexts the architecture already had, Assignment, Assessment, Grading, and Course, each with its own product owner, frontend and backend engineers, QE automation, and embedded infrastructure and security. This is Team Topologies' stream-aligned team pattern in practice: a team that owns a domain end to end ships faster than a team that owns a layer and has to coordinate with three other layer-owning teams to change anything a customer actually notices.

Modernizing inline: putting AI into the platform while we built it

The unified API and data layer was not just an efficiency argument for the board. It was what let generative AI ship as part of the convergence itself rather than as a fifth integration effort tacked on afterward, and the path to get there had its own lesson about vendor risk, the same lesson the earlier failed rewrite had already taught the organization once.

The proposal: build it, don't rent it

When large language models became commercially viable in late 2022 and early 2023, the portfolio had no internal expertise in the space and a leadership team newly aware of the risk of falling behind. The first instinct was to buy the capability: evaluate vendors, sign a contract, and let someone else own the risk. That path stalled for reasons that echoed the earlier platform rewrite almost exactly. The vendors under evaluation could demonstrate a chatbot; none of them had domain expertise in academic content, assessment integrity, or the regulatory obligations specific to an education product, and the contracts on offer were priced and timelined as if that expertise did not matter. Paying someone else to build something core to the business, without in-house expertise to hold them accountable for it, had already gone wrong once in this organization's recent memory, and the board recognized the pattern before it needed spelling out.

So the proposal I put in front of the executive team was to build the capability internally, inside the platform already being converged, and to treat the first release as a scoped proof of concept rather than a roadmap commitment. That framing mattered for approval: it let a newly cautious leadership team say yes to a small, reversible bet instead of a large, uncertain one. It also meant naming the open problems honestly instead of glossing over them. A model that reasons well can still hallucinate or answer with unwarranted confidence. Context windows and token limits cap how much of a course a single request can actually see. Guardrails for an education product were still an emerging discipline, not a solved one. And every one of the four legacy platforms the tools eventually had to reach into was itself still part of the problem the convergence was in the middle of fixing. None of that was a reason to wait; it was the actual list of engineering work, and naming it up front is what made the plan credible instead of aspirational.

The study assistant: two pipelines, not one

A small team stood up in February 2023 to run a proof of concept and demo it to the executive team. By that fall, the team had shipped a Study Assistant embedded directly in coursework, built around two separate pipelines rather than one, because ingestion and live conversation have completely different cost and latency profiles, and serving both from a single model wastes money on every request.

The ingestion pipeline runs once per piece of content, not once per student interaction. Textbook chapters, assessment items, and existing course material are pulled from each of the four platforms' content stores, normalized into a common schema, and enriched using a large multimodal model capable of reading dense academic material, tables, and diagrams, then written into a vector store built for semantic search. That normalization step is what let content built on four different authoring systems, one of them a legacy desktop tool with almost no documentation, become searchable through a single interface for the first time.

The runtime pipeline is what a student actually talks to, and it deliberately runs on a smaller, faster model than the one doing ingestion. A student's question comes in through the same micro-frontend and backend-for-frontend layer the rest of the converged platform uses, gets grounded against the vector store built during ingestion, and returns a response in the low seconds instead of the ten-plus seconds a heavier model would cost on every turn. Conversation history and feedback are stored separately from the content itself, which is what makes the tutoring personal to a given student without touching the shared knowledge base to do it.

INGESTION, ONCE PER CONTENT ITEM Course content all four platforms Normalize + enrich multimodal model reads text, tables, figures Vector store semantic search RUNTIME, EVERY STUDENT TURN Student question micro-frontend + BFF retrieval Fast runtime model grounded on retrieval, answers in low seconds Response + feedback stored apart from content Ingestion cost is paid once per piece of content, not once per student turn. A heavier model does the reading; a lighter, faster model does the talking.
The study assistant as two pipelines: ingestion runs once per piece of content on a model built for reading, runtime runs on every student turn on a model built for speed.

The instructor tool: an agent that calls the platform's own services

The architecture got revised for a second, more ambitious tool aimed at instructors, launched the following spring. Where the study assistant answers questions, the instructor tool does something closer to work: given a plain-language request such as build a homework set on linear equations, it has to find the right section of the course, pull validated questions that match it, and assemble a complete assignment, which is a multi-step task rather than a single lookup.

We built it as a ReAct agent, reasoning and acting in a loop rather than answering in one shot. The model forms a thought about what it needs, takes an action, either calling a tool against a live domain service or retrieving from a knowledge base, observes what came back, and decides whether it has enough to finish or needs another step. That loop can run several times before the agent returns a final answer.

Two different grounding sources feed that loop, and keeping them separate turned out to matter. Structured actions, look up a table of contents, search for validated questions, save a finished assignment, go through tool calls against the platform's own course, assignment, and assessment services, the same domain services the rest of the converged platform used. Open-ended content questions, what does this chapter actually cover, find material similar to this passage, go through retrieval-augmented generation against a knowledge base built on the same content vector store the study assistant's ingestion pipeline already populated, so a second index never had to be built and kept in sync with the first. Tools give the agent hands; retrieval gives it eyes on the actual textbook. Neither alone would have been enough, and presenting both as resources it could reach for, rather than forcing a choice between them, meant the agent's answers were grounded in real content instead of guessing at plausible-looking material. Every request, every tool call, and every retrieval in between are logged to a conversation and event store, which is what let the team debug a bad output by replaying exactly what the agent saw and decided, instead of guessing after the fact.

Instructor request build a homework set Content knowledge base same vector store as the study assistant's ingestion RAG REACT AGENT Thought, then Action, then Observation repeats until done action action Course service table of contents Assessment service validated questions Assignment service saves the finished set Final answer logged with every step taken
The instructor agent loops through thought, action, and observation, drawing on retrieval-augmented grounding from a shared content knowledge base as well as tool calls against the platform's own domain services, until it has enough to hand back a finished assignment.

Guardrails as shared infrastructure

Guardrails sat in front of both tools as a shared, reusable layer rather than something built twice, another benefit of treating the AI engine as a peer to the domain services instead of a bolt-on inside any single one of the four original platforms. Content filtering and redaction of personally identifiable information ran on every request before a response reached a student or instructor, with the specific list of restricted topics configurable rather than hardcoded, since what counts as off-limits in a mathematics course is not the same list as in a nursing or psychology course. The harder design problem was pedagogical, not technical: system prompts for the study assistant were built specifically to walk a student through a problem step by step without disclosing the answer outright, because a study tool that just hands over answers is not a study tool, it is a way to erode the academic integrity the whole platform depends on. That single constraint got tested and rewritten more times than any other part of either tool before it shipped.

Extending the pattern: an orchestrated set of agents

A single ReAct agent covers a lot of ground, but a real instructor request does not always stay inside one domain. Update the gradebook weighting to match a new assignment and flag students now below the drop threshold touches assignment, grading, and communication in one sentence, and forcing one agent to hold tool definitions and system prompt guidance for every domain at once is how prompts get bloated and tool selection gets unreliable. We designed the architecture to extend into an orchestrated set of agents rather than one agent that keeps growing: a lightweight classifier reads the request first and routes it to the agent scoped to it, a course agent, an assignment agent, an assessment agent, a grading agent, each carrying only the tools, retrieval scope, and system prompt relevant to its own domain, the same four bounded contexts the domain services and the two-pizza squads were already built around.

What makes it an orchestrated set rather than four separate chatbots wearing a shared skin is state. Every agent reads from and writes to the same conversation history and memory store, so a request that spans domains gets handled as a short sequence instead of a dead end: the classifier hands off to the assignment agent, its output becomes context for the grading agent, and the response comes back stitched together without the instructor repeating themselves or restating context an earlier agent already established. The classifier itself stayed deliberately simple, an intent match rather than a full model call, because routing has to be fast and cheap on every single request, while the reasoning budget gets spent inside whichever specialized agent actually owns the task.

Instructor request Classifier Course agent own tools, own prompt Assignment agent own tools, own prompt Assessment agent own tools, own prompt Grading agent own tools, own prompt Shared conversation history and memory, every agent reads and writes here Response stitched by the classifier
Beyond one agent: a classifier routes each request to the agent scoped to its bounded context, every agent shares the same memory, and a multi-domain request comes back as one stitched response.

What broke, and what the lessons actually were

Executing a convergence at this scale surfaces problems no architecture textbook prepares you for.

  1. Dual-write races. Writing to the legacy SQL Server instance and the new domain stores at the same time introduced race conditions during Phase 2, exactly where the pattern warns you they will show up. We resolved it with a strict primary-secondary write hierarchy, idempotency keys on every write, and reconciliation jobs running out-of-band to catch and correct drift rather than trying to prevent every possible race in real time.
  2. Undocumented stored procedures. With 3,889 procedures accumulated over a decade, the written documentation could not be trusted on its own. Runtime telemetry traced real call paths, parameter distributions, and execution frequency across a full academic quarter before we reimplemented any of that logic inside the domain services, which is closer to forensic analysis than to a migration checklist.
  3. Feature-flag safety nets. If the error rate on a newly routed micro-frontend crossed a small threshold, roughly one in two thousand requests, traffic reverted to the legacy path automatically, without a redeploy and without anyone paging in the middle of the night to make the call. Progressive delivery only works as a real safety net if the rollback is automatic and instant; a rollback that requires a human to notice and act is not actually a safety net, it is a slower version of the same outage.

What changed, measured

Four platforms became one application, built on domain-driven microservices behind a shared API and data layer, with a 26TB, unsharded database that had been a single point of both risk and contention now decomposed into stores chosen for how each domain actually used them. Deployment frequency moved from quarterly releases to multiple deployments a day, the metric DORA's research consistently ties to both delivery speed and change stability, because smaller, flagged changes are inherently less risky than infrequent, large ones. The $750M combined revenue base, and the $400M carried by the anchor platform alone, moved through every phase of the migration without an outage tied to the transformation itself. And because the AI engine was designed as part of the convergence rather than after it, the Study Assistant and Instructor tools launched against the same unified data and domain layer every other part of the platform used, instead of requiring their own separate integration into each of the four original systems.

The technical pattern here, strangler fig migration, domain-driven decomposition, polyglot persistence, is well established and was not the hard part in hindsight. The hard part was the leadership work that had to happen before a line of the new architecture existed: reading the entire estate personally instead of trusting four secondhand summaries, understanding exactly why the previous attempt had failed before proposing anything that could be mistaken for a repeat of it, and building a funding case around the numbers the business already worried about rather than the architecture that happened to interest me most. Anyone leading one of these should expect the org chart and the funding conversation to be harder than the migration itself, because they usually are.