[{"data":1,"prerenderedAt":555},["ShallowReactive",2],{"insight-legacy-erp-anticorruption-layer":3,"insights-cards":445},{"id":4,"title":5,"body":6,"category":426,"date":427,"description":428,"extension":429,"meta":430,"navigation":431,"path":432,"readingTime":433,"relatedArticles":434,"relatedAssessment":438,"relatedServices":439,"seo":442,"stem":443,"__hash__":444},"insights\u002Finsights\u002Flegacy-erp-anticorruption-layer.md","The ERP stays as it is: building on a decades-old system through an anti-corruption layer",{"type":7,"value":8,"toc":414},"minimark",[9,13,18,48,55,58,62,65,85,91,94,98,101,183,186,189,222,226,229,232,253,256,260,263,266,269,301,304,307,310,317,321,324,327,365,369,372,404,407,411],[10,11,12],"p",{},"You do not have to change an old ERP to build modern software on top of it, and in most cases you should not try. What you need is one deliberately designed boundary: facing the ERP, it uses the ERP's vocabulary, and facing your new applications, it uses the terms your business works in today. That boundary is called an anti-corruption layer, and the quality of its design decides whether your new systems stay clean or slowly turn into extensions of the old one.",[14,15,17],"h2",{"id":16},"the-problem-it-solves","The problem it solves",[10,19,20,21,25,26,29,30,29,33,36,37,40,41,43,44,47],{},"An ERP that has run a company for twenty years carries twenty years of decisions in its data model. Field names are eight characters long. A column called ",[22,23,24],"code",{},"CUSTTYPE"," holds ",[22,27,28],{},"A",", ",[22,31,32],{},"B",[22,34,35],{},"7"," and ",[22,38,39],{},"X",", and only two people know that ",[22,42,35],{}," means \"tax exempt, do not ship on Fridays\". Dates are stored as integers. Deleted records are not deleted, they have a ",[22,45,46],{},"D"," in a status column. Quantities have implied decimals. A customer with three shipping addresses exists as three customer records with a naming convention.",[10,49,50,51,54],{},"None of this is a defect in the sense that matters to the business. The ERP closes the books every month. But when a new application reads those tables directly, every one of those conventions leaks into it. Soon the new order portal has ",[22,52,53],{},"if custType == '7'"," scattered through its code, the mobile app has its own copy of the same rule (slightly different), and the reporting service has a third. You now have several systems that all depend on undocumented legacy quirks, and changing or replacing the ERP has become harder than it was before you started.",[10,56,57],{},"The term comes from Eric Evans's work on domain-driven design. The idea is simple: when two systems have different models of the world, do not let one model seep into the other. Put a translator between them, and make the translator the only place that knows both.",[14,59,61],{"id":60},"what-the-layer-is-concretely","What the layer is, concretely",[10,63,64],{},"Forget the name for a moment. In practice it is a small service (or a module with a strict interface) with two faces.",[10,66,67,71,72,29,75,29,78,29,81,84],{},[68,69,70],"strong",{},"The inward face"," is an API or set of events designed around how your business talks today. It exposes ",[22,73,74],{},"Customer",[22,76,77],{},"Order",[22,79,80],{},"Shipment",[22,82,83],{},"Invoice",", with clear field names, real types, ISO dates, explicit currencies and documented states. It knows nothing about table names.",[10,86,87,90],{},[68,88,89],{},"The outward face"," talks to the ERP in whatever way the ERP allows: SQL against its database or a replica, a vendor API if one exists, stored procedure calls, flat file import and export, a message queue, sometimes a terminal session.",[10,92,93],{},"Between the two sits the translation. New applications only ever see the inward face. If the ERP is upgraded, re-platformed or eventually replaced, the outward face and the translation change. The applications do not.",[14,95,97],{"id":96},"what-belongs-inside-and-what-does-not","What belongs inside, and what does not",[10,99,100],{},"The layer is useful only if it has a narrow job. A practical split:",[102,103,104,117],"table",{},[105,106,107],"thead",{},[108,109,110,114],"tr",{},[111,112,113],"th",{},"Belongs in the layer",[111,115,116],{},"Belongs somewhere else",[118,119,120,129,137,145,153,161,169,176],"tbody",{},[108,121,122,126],{},[123,124,125],"td",{},"Mapping legacy fields and codes to clean names and enumerations",[123,127,128],{},"Pricing rules, approval rules, any new business logic",[108,130,131,134],{},[123,132,133],{},"Type and unit conversion (integer dates, implied decimals, padded strings, code pages)",[123,135,136],{},"Workflow and orchestration across several systems",[108,138,139,142],{},[123,140,141],{},"Reassembling one business entity from many legacy rows or tables",[123,143,144],{},"User interface concerns and presentation formatting",[108,146,147,150],{},[123,148,149],{},"Splitting one clean entity into the records the ERP expects",[123,151,152],{},"Reporting and analytics queries",[108,154,155,158],{},[123,156,157],{},"Protocol handling: connection pooling, retries, timeouts, batching",[123,159,160],{},"New data that the ERP has no place for",[108,162,163,166],{},[123,164,165],{},"Translating ERP errors into meaningful domain errors",[123,167,168],{},"Features of the new applications",[108,170,171,174],{},[123,172,173],{},"Protecting the ERP: rate limits, concurrency caps, batch windows",[123,175],{},[108,177,178,181],{},[123,179,180],{},"Logging every call that crosses the boundary",[123,182],{},[10,184,185],{},"The left column is all translation and protection. The moment a rule in the layer answers the question \"what should the business do?\" instead of \"how does the ERP represent this?\", it is in the wrong place.",[10,187,188],{},"A few details that are easy to miss:",[190,191,192,199,216],"ul",{},[193,194,195,198],"li",{},[68,196,197],{},"Identity mapping."," The ERP's keys are often composite, reused or meaningful (a customer number with the branch encoded in it). The layer should issue or map stable identifiers so that new systems never parse a legacy key.",[193,200,201,204,205,207,208,211,212,215],{},[68,202,203],{},"Code tables as data."," Keep the mapping of ",[22,206,35],{}," to ",[22,209,210],{},"TAX_EXEMPT"," in a versioned table or config, not in an ",[22,213,214],{},"if"," statement. Somebody will add a new code in the ERP without telling you. The layer should detect an unknown code, log it loudly and fail in a defined way, not pass it through.",[193,217,218,221],{},[68,219,220],{},"The undocumented rules."," Spend time with the people who use the ERP daily. The rule that an order with a blank ship date means \"hold\" is not in any manual. Capture each one as a test against the layer.",[14,223,225],{"id":224},"reading-is-the-easy-half","Reading is the easy half",[10,227,228],{},"Reads have a comfortable property: if you get them wrong, you can fix the code and read again.",[10,230,231],{},"The main design question for reads is where they hit. Three options, in rough order of preference:",[233,234,235,241,247],"ol",{},[193,236,237,240],{},[68,238,239],{},"A replica or replicated copy."," Queries from new applications never touch the production ERP database. You accept a delay, which you should measure and publish (\"inventory is at most five minutes old\").",[193,242,243,246],{},[68,244,245],{},"A cache or read model owned by the layer."," The layer pulls changes on a schedule or through change data capture and serves reads from its own store. This also lets you shape the data for the new applications.",[193,248,249,252],{},[68,250,251],{},"Direct reads against production."," Sometimes unavoidable for data that must be current to the second, such as available credit at order time. Cap the concurrency, set timeouts and keep the queries simple, because a slow report from your new portal should never be the reason month-end close stalls.",[10,254,255],{},"Be explicit about freshness for each kind of data. Product descriptions can be a day old. Stock on hand usually cannot.",[14,257,259],{"id":258},"writes-are-where-the-design-is-decided","Writes are where the design is decided",[10,261,262],{},"Writing into an old ERP is a different kind of problem, for one reason: the ERP's own screens and programs enforce rules that the database does not.",[10,264,265],{},"When a clerk enters an order through the ERP, the application code checks credit, reserves stock, assigns a number from a sequence, updates three summary tables and queues a pick ticket. If your layer inserts a row straight into the order table, none of that happens. The row exists, the totals are wrong, and the warehouse never sees it. Depending on the vendor, direct writes may also put your support agreement at risk, which is a question for your contract and your vendor, not for a guess.",[10,267,268],{},"So the first rule of writes is to go through whatever path runs the ERP's own logic. In order of preference:",[233,270,271,277,283,289,295],{},[193,272,273,276],{},[68,274,275],{},"A supported API or business object interface",", if the ERP has one.",[193,278,279,282],{},[68,280,281],{},"A supported import mechanism",": a staging table, an interface file, an EDI-style inbound process that the ERP validates and posts itself.",[193,284,285,288],{},[68,286,287],{},"Stored procedures the vendor or your team already maintains"," for that transaction.",[193,290,291,294],{},[68,292,293],{},"Driving the user interface with automation",", as a last resort, wrapped inside the layer so that nothing else knows it is happening.",[193,296,297,300],{},[68,298,299],{},"Direct table writes",", only for tables you fully understand, with the ERP owner's agreement, and almost never for transactional data.",[10,302,303],{},"The second rule is that every write must be safe to retry. Networks fail between \"sent\" and \"confirmed\". The layer needs an idempotency key per business operation, stored on its side, so that a retried order submission is recognized and not posted twice.",[10,305,306],{},"The third rule is to accept that many legacy write paths are asynchronous. A file dropped at 2:10 pm is picked up at 2:15 and posted, or rejected, a few minutes later. Design the inward API to reflect that honestly: the caller gets \"accepted, pending\" with a reference, and learns the outcome through a status query or an event. Pretending the write is synchronous leads to timeouts and to users who click submit three times.",[10,308,309],{},"A reliable shape for this is an outbox: the new application records the intent in its own database in the same transaction as its own changes, a worker in the layer delivers it to the ERP, and the result (posted, rejected with reason) is written back. Add a daily reconciliation job that compares what the new systems believe was sent with what the ERP actually holds. That job will earn its keep the first week.",[10,311,312,313,316],{},"Picture a manufacturer whose customer portal lets buyers place orders. The portal calls ",[22,314,315],{},"POST \u002Forders"," on the layer. The layer validates the shape, maps products and units to ERP item codes, writes a record to the ERP's order import staging table and returns a pending reference. The ERP's nightly and hourly import jobs apply credit and stock rules. Rejections come back as status rows, which the layer translates into \"credit hold\" or \"item discontinued\" and passes to the portal. The ERP's code was never touched, and every rule it enforces still applies.",[14,318,320],{"id":319},"keeping-the-layer-thin","Keeping the layer thin",[10,322,323],{},"Boundary layers have a natural tendency to grow, because they are the one place everything passes through and adding \"one small rule\" there is always the quickest fix. A year later the layer is a second ERP with no documentation.",[10,325,326],{},"Habits that prevent it:",[190,328,329,335,341,347,353,359],{},[193,330,331,334],{},[68,332,333],{},"One reason to change."," The layer changes when the ERP's representation changes or when a new entity needs translating. A change request that begins with \"the business wants\" belongs in a domain service.",[193,336,337,340],{},[68,338,339],{},"No orchestration."," If an operation involves the ERP, the CRM and the shipping carrier, a separate service coordinates them. The layer only handles its own system.",[193,342,343,346],{},[68,344,345],{},"Minimal state."," Idempotency keys, identity maps, an outbox and possibly a read cache. No business records of its own.",[193,348,349,352],{},[68,350,351],{},"Contract tests against the real thing."," Keep a test suite that runs against a non-production copy of the ERP, with recorded samples of ugly real data. ERP patches and custom report changes break integrations silently, and this suite is how you find out before users do.",[193,354,355,358],{},[68,356,357],{},"A named owner."," Someone is responsible for the mapping tables and for saying no.",[193,360,361,364],{},[68,362,363],{},"Publish only what is used."," Expose the five entities the new applications need, not all four hundred tables. Each exposed field is a promise you must keep.",[14,366,368],{"id":367},"when-you-can-skip-it","When you can skip it",[10,370,371],{},"A translation layer is an extra component to build, deploy and monitor. Sometimes it is not worth having.",[190,373,374,380,386,392,398],{},[193,375,376,379],{},[68,377,378],{},"One small, short-lived integration."," A single nightly export to one consumer does not need an architecture. A well-commented script and a mapping file are fine.",[193,381,382,385],{},[68,383,384],{},"The ERP's model is already close to yours."," If the system is reasonably modern, has a documented API and uses concepts that match how you work, a thin client library is enough.",[193,387,388,391],{},[68,389,390],{},"Analytics only."," If all you need is reporting, load the data into a warehouse and do the cleanup in transformation models. That is a translation layer of a kind, built with different tools.",[193,393,394,397],{},[68,395,396],{},"Replacement is imminent and committed."," If the ERP is being switched off in six months with budget approved, invest in the migration instead.",[193,399,400,403],{},[68,401,402],{},"The vendor's integration layer already does the job."," If a supported integration product gives you a clean, stable contract, use it and keep your own code on the far side of it.",[10,405,406],{},"The warning sign that you do need one: a second new application is about to integrate with the ERP, and the plan is to copy the SQL from the first.",[14,408,410],{"id":409},"how-many-of-your-systems-know-what-your-erps-column-names-mean","How many of your systems know what your ERP's column names mean?",[10,412,413],{},"If the answer is more than one, the ERP's quirks are already spreading. It is worth mapping where, and deciding what a clean boundary would look like before the next application gets built on top. Trivo can do that mapping with your team and hand you a written design you own.",{"title":415,"searchDepth":416,"depth":416,"links":417},"",2,[418,419,420,421,422,423,424,425],{"id":16,"depth":416,"text":17},{"id":60,"depth":416,"text":61},{"id":96,"depth":416,"text":97},{"id":224,"depth":416,"text":225},{"id":258,"depth":416,"text":259},{"id":319,"depth":416,"text":320},{"id":367,"depth":416,"text":368},{"id":409,"depth":416,"text":410},"Modernization","2026-10-12","How to build new software on an old ERP without modifying it, using an anti-corruption layer: what goes inside, how to handle writes, when to skip it.","md",{},true,"\u002Finsights\u002Flegacy-erp-anticorruption-layer",9,[435,436,437],"strangler-fig-modernization","rpa-vs-api-integration","etl-vs-elt","legacy-risk",[440,441],"modernization","custom-software",{"title":5,"description":428},"insights\u002Flegacy-erp-anticorruption-layer","q423bS8wWu7okTb_xwyqnswRvK-66dbFVCCkiyrZkJg",[446,452,459,464,469,474,482,487,492,497,503,510,515,521,527,529,535,540,545,550],{"path":447,"title":448,"description":449,"category":450,"readingTime":433,"relatedServices":451,"date":427},"\u002Finsights\u002Fbuild-vs-buy","The License Fee Is the Small Number: A Working Guide to Build or Buy","Buying software is usually right, until the product sits on top of how you make money. How to sort capabilities, price the hidden costs and decide.","Strategy",[441,440],{"path":453,"title":454,"description":455,"category":456,"readingTime":433,"relatedServices":457,"date":427},"\u002Finsights\u002Fcloudflare-observability","The Cloudflare Signals You Need Wired Up Before Something Breaks","Cloudflare records plenty by default. Whether a person hears about a problem in time depends on choices made in a quiet week, not mid-outage.","Cloudflare",[458,440],"cloudflare",{"path":460,"title":461,"description":462,"category":456,"readingTime":433,"relatedServices":463,"date":427},"\u002Finsights\u002Fcloudflare-performance-tuning","After the Static Assets: Where Cloudflare Performance Is Actually Won","Default caching handles images, CSS and JavaScript. The larger gains sit in HTML, API responses, cache keys and origin trips that nobody tuned.",[458,441],{"path":465,"title":466,"description":467,"category":456,"readingTime":433,"relatedServices":468,"date":427},"\u002Finsights\u002Fcloudflare-waf-and-ddos","Managed Rules Know the Internet. Custom Rules Have to Know Your Application.","Cloudflare's managed WAF and DDoS rules stop generic attacks. Logins, APIs, bots and false positives still need rules written from your own traffic.",[458,441],{"path":470,"title":471,"description":472,"category":456,"readingTime":433,"relatedServices":473,"date":427},"\u002Finsights\u002Fcloudflare-zero-trust-and-integrity","Who Got In Versus What Is Running: Two Integrity Problems One Control Cannot Solve","Zero Trust verifies who reaches a system. Client-side script monitoring verifies what runs in the browser. They fail differently and need separate owners.",[458,440],{"path":475,"title":476,"description":477,"category":478,"readingTime":479,"relatedServices":480,"date":427},"\u002Finsights\u002Fcrm-adoption-and-training","Your CRM Passed Every Test and Nobody Uses It: A Diagnostic for Stalled Adoption","Low CRM adoption is rarely a training gap. How to tell whether the cause is screen friction, missing payback or management habits, and what to fix first.","CRM",8,[481],"crm",{"path":483,"title":484,"description":485,"category":478,"readingTime":479,"relatedServices":486,"date":427},"\u002Finsights\u002Fcrm-implementation-timeline","The Honest Calendar of a CRM Implementation: Five Phases and Where the Weeks Go","Provisioning a CRM takes a day. Making it the place your team sells from takes months. A phase-by-phase calendar, with the parts quotes tend to leave out.",[481,441],{"path":488,"title":489,"description":490,"category":478,"readingTime":433,"relatedServices":491,"date":427},"\u002Finsights\u002Fcrm-migration-checklist","CRM Migration Checklist: The Records Arrive, the Selling Process Gets Left Behind","A CRM migration is a process migration with a data load attached. What a plain export loses, how to choose a cutover, and a checklist for each stage.",[481,440],{"path":493,"title":494,"description":495,"category":478,"readingTime":479,"relatedServices":496,"date":427},"\u002Finsights\u002Fcustom-crm-vs-off-the-shelf","Should You Build Your Own CRM? Run the Build-or-Buy Test on Your Sales Process","Most of a CRM is a commodity you should buy. The question is whether the way you sell fits the platform's model, and what it costs when it does not.",[481,441],{"path":498,"title":499,"description":500,"category":450,"readingTime":501,"relatedServices":502,"date":427},"\u002Finsights\u002Fcustom-software-cost-framework","Estimate Before the Estimate: Sizing a Custom Software Budget on Your Own","You can't know the price before a quote, but you can know what drives it. Six cost drivers, a self-check worksheet and how to read a proposal.",10,[441],{"path":504,"title":505,"description":506,"category":507,"readingTime":433,"relatedServices":508,"date":427},"\u002Finsights\u002Fetl-vs-elt","Transform before or after loading? A practical ETL versus ELT decision for mid-sized teams","ETL or ELT is a question of where transformation runs and who pays for it. A practical way for a mid-sized team to choose, with the costs most plans miss.","Data",[441,509],"ai-automation",{"path":511,"title":512,"description":513,"category":450,"readingTime":501,"relatedServices":514,"date":427},"\u002Finsights\u002Fevaluating-engineering-depth","Ask About the Outage: How to Vet a Software Vendor's Real Engineering Ability","Portfolios and logos look the same from every vendor. The interview questions, artifacts and deal-breakers that show who has really run software in production.",[441,440],{"path":516,"title":517,"description":518,"category":519,"readingTime":501,"relatedServices":520,"date":427},"\u002Finsights\u002Fhipaa-business-associate-agreement","A BAA Is a Contract, Not a Badge: What Your Software Vendor Is Really Signing Up For","What a HIPAA Business Associate Agreement commits a software vendor to, what it leaves unproven, and what to ask and build before PHI reaches the system.","Compliance",[441,509],{"path":522,"title":523,"description":524,"category":519,"readingTime":525,"relatedServices":526,"date":427},"\u002Finsights\u002Fhipaa-technical-safeguards-checklist","Reading 45 CFR 164.312 Like a Design Document: HIPAA Technical Safeguards for Engineers","The five HIPAA technical safeguard standards mapped to concrete engineering decisions: identity, logging, integrity, authentication and encryption.",11,[441,440,458],{"path":432,"title":5,"description":428,"category":426,"readingTime":433,"relatedServices":528,"date":427},[440,441],{"path":530,"title":531,"description":532,"category":533,"readingTime":501,"relatedServices":534,"date":427},"\u002Finsights\u002Fllm-in-production-confidence-thresholds","An LLM that proposes and a system that decides: thresholds, review and audit for production AI","Running an LLM in production without giving it autonomy: measured confidence, thresholds set by business cost, human review that works, an audit trail.","AI",[509,441],{"path":536,"title":537,"description":538,"category":450,"readingTime":433,"relatedServices":539,"date":427},"\u002Finsights\u002Fnearshore-offshore-onshore-cost","Cheap Hours, Expensive Projects: Pricing US, Nearshore and Offshore Teams Honestly","The hourly rate tells you little about what a project will cost. How overlap, rework, turnover and compliance change the math for each delivery model.",[441],{"path":541,"title":542,"description":543,"category":533,"readingTime":433,"relatedServices":544,"date":427},"\u002Finsights\u002Frpa-vs-api-integration","Bots on the screen or calls to the API: where RPA earns its place and where it becomes debt","RPA and API integration solve the same problem at different layers. How to choose, what screen automation really costs, and how to run it safely if you must.",[509,441],{"path":546,"title":547,"description":548,"category":426,"readingTime":433,"relatedServices":549,"date":427},"\u002Finsights\u002Fstrangler-fig-modernization","Replace the legacy system in slices: a working guide to the strangler fig pattern","How to retire a legacy system piece by piece while the business keeps running: the facade, the first slice, parallel runs, and when the pattern fails.",[440,441],{"path":551,"title":552,"description":553,"category":450,"readingTime":501,"relatedServices":554,"date":427},"\u002Finsights\u002Ftwo-week-discovery-deliverables","Paying for a Plan, Not a Pitch: What Two Weeks of Discovery Owes You","A paid discovery should leave you with documents another team could build from. The five deliverables, the work behind them and the limits to expect.",[441,440],1792618973203]