Deepen PracticeOrdered learning track

Configuration as Domain and Runtime Behavior

Learn Java Large Scale ERP - Part 021

Configuration as domain and runtime behavior for large-scale Java ERP systems, including effective-dated configuration, governance workflow, runtime evaluation, caching, auditability, rollout, and failure modelling.

23 min read4495 words
PrevNext
Lesson 2134 lesson track1928 Deepen Practice
#java#erp#configuration#enterprise-architecture+4 more

Part 021 — Configuration as Domain and Runtime Behavior

1. Why This Part Matters

Large-scale ERP systems rarely fail because there is no application.yml. They fail because business behavior becomes configurable without governance.

A small application can treat configuration as environment values:

  • database URL;
  • timeout;
  • feature flag;
  • log level;
  • service endpoint;
  • thread pool size.

A large ERP must treat many configuration values as business facts with legal, financial, operational, and audit consequences:

  • which approval matrix was effective when a purchase requisition was submitted;
  • which tax rule was active when an invoice was posted;
  • which fiscal period lock prevented or allowed backdated posting;
  • which cost center mapping was used to generate accounting entries;
  • which numbering sequence generated a legal invoice number;
  • which warehouse policy allowed negative stock;
  • which entity-specific price override was active during quotation;
  • which country localization pack determined document behavior;
  • which integration route sent a payment file to a bank;
  • which tenant-level policy allowed a document to skip manual approval.

If we model all of those as “just configuration”, we lose control. If we hard-code all of them, the ERP becomes unchangeable.

The engineering problem is to design configuration as a runtime domain model:

Configuration is controlled business behavior that can vary by scope, time, version, environment, tenant, legal entity, and operational context, while remaining explainable, testable, auditable, and reversible.

This part focuses on that boundary.


2. Kaufman Skill Deconstruction

Following Josh Kaufman’s learning strategy, we deconstruct “configuration” into smaller skills that can be practiced deliberately.

Sub-skillWhat You Must LearnERP Failure If Ignored
Configuration taxonomySeparate technical config, business config, policy, rules, master data, and feature flagsEverything becomes a key-value table
Scope modellingApply config by tenant, legal entity, branch, country, warehouse, role, or document typeWrong behavior leaks across organizations
Effective datingMake config valid by time and lifecycle stateOld documents are recalculated with new rules
VersioningPreserve what changed, when, by whom, and whyAudit cannot explain behavior
Publication workflowDraft, validate, approve, publish, activate, retireUnreviewed config changes impact production
Runtime evaluationResolve config deterministically for a business operationSame request behaves differently under load
Caching and propagationKeep performance without stale dangerous behaviorApp nodes disagree on approval/tax/posting rules
TestingTest config as executable behaviorBusiness change breaks financial or operational rules
ObservabilityExplain which config was used in each decisionSupport cannot debug “why did ERP do this?”
GovernanceControl ownership, SoD, and emergency changesBusiness users silently change core behavior

The target skill is not memorizing “use feature flags” or “store config in DB”. The target skill is knowing which behavior belongs where and how to make runtime variability safe.


3. Configuration Is Not One Thing

A common mistake is to put everything under the word “configuration”. That hides important differences.

3.1 Configuration Taxonomy

TypeExampleOwnerChange FrequencyRuntime RiskStorage Style
Technical environment configJDBC URL, broker URL, HTTP timeoutPlatform/SREMediumOperationalenv vars, config server, secret manager
Application behavior configenable async posting, default page sizeEngineering/ProductMediumOperational + functionaltyped config, feature management
Business policy configapproval threshold, credit hold policyBusiness ownerHighFinancial/controlgoverned config domain
Calculation rule configtax rule, pricing rule, depreciation methodDomain owner + finance/legalMediumFinancial/legalrule table/versioned rule set
Master dataitem, vendor, customer, account, UOMData stewardHighOperational + financialmaster data module
Reference datacurrency, country, tax jurisdiction, document typeData governanceLow-mediumCross-systemgoverned reference catalog
Localization configcountry-specific numbering, tax invoice behaviorLocal compliance ownerMediumLegalcountry pack/config package
Workflow configapproval graph, SLA, escalation, delegationProcess ownerMediumControl/auditworkflow definition repository
Integration configendpoint route, partner mapping, bank formatIntegration ownerMediumData movementintegration registry
Feature flaggradual rollout, kill switchProduct/PlatformHighFunctional/operationalfeature flag platform
SecretAPI key, database password, certificateSecurity/PlatformMediumSecuritysecret manager

The first design rule:

Do not let technical configuration infrastructure become the business configuration model.

Spring Boot externalized configuration is excellent for application and environment configuration, but business rules such as tax behavior or approval thresholds need a domain model with audit, effective dating, validation, and ownership.


4. Mental Model: Configuration as a Decision Function

In an ERP, configuration is usually not “read value by key”. It is a decision function.

result = resolve(configuration_type, business_context, evaluation_time)

For example:

approval_limit = resolve(
  type = PURCHASE_APPROVAL_LIMIT,
  context = {
    tenant: T1,
    legalEntity: ID-PT-01,
    branch: JKT,
    department: IT,
    role: PROCUREMENT_MANAGER,
    currency: IDR,
    documentType: PURCHASE_REQUISITION,
    amount: 150_000_000
  },
  evaluationTime = 2026-07-01T10:15:00+07:00
)

The output should not just be the number 100_000_000. It should include the explanation:

{
  "value": 100000000,
  "currency": "IDR",
  "matchedRuleId": "APR-LIMIT-2026-Q3-00042",
  "ruleVersion": 7,
  "scope": "tenant=T1/legalEntity=ID-PT-01/department=IT/role=PROCUREMENT_MANAGER",
  "effectiveFrom": "2026-07-01T00:00:00+07:00",
  "approvedBy": "finance.control@example.com",
  "publishedAt": "2026-06-28T14:00:00+07:00"
}

The explanation is part of the result because ERP decisions must be supportable and defensible.


5. Core Invariants of ERP Configuration

These invariants should guide every configuration design.

5.1 Determinism

For the same input context and evaluation time, configuration resolution must return the same result.

Bad:

Approval threshold depends on whichever cache entry was loaded first.

Good:

Approval threshold is resolved from a published version by scope and effective time.

5.2 Explainability

Every business-impacting configuration result must be explainable.

The system must answer:

  • which config was used;
  • why it matched;
  • which broader/default rule was overridden;
  • when it became effective;
  • who approved it;
  • whether it was active, retired, or emergency-published;
  • which document captured the snapshot.

5.3 Temporal Correctness

Historical documents must remain bound to historical behavior.

A posted invoice from March must not silently change tax calculation because the tax config was updated in July.

5.4 Scope Isolation

Config must not leak across tenant, legal entity, branch, country, or module boundary.

This matters especially in multi-company ERP.

5.5 Controlled Mutability

Configuration may change, but only through governed lifecycle transitions.

DRAFT -> VALIDATED -> APPROVED -> PUBLISHED -> ACTIVE -> RETIRED

Direct production edits are a red flag.

5.6 Fail-Safe Behavior

If config cannot be resolved, ERP must fail in a safe mode.

Examples:

  • missing approval policy: block submission;
  • missing tax rule: block invoice posting;
  • missing report label: degrade gracefully;
  • missing optional email template: use default;
  • missing integration route: queue for exception, do not silently drop.

Not every missing config is equally dangerous.


6. Configuration Architecture Overview

A robust ERP does not let domain services read arbitrary configuration tables directly. Domain services ask a resolver for a typed decision.

The resolver handles:

  • scope matching;
  • effective date;
  • version selection;
  • fallback hierarchy;
  • conflict detection;
  • cache consistency;
  • audit explanation;
  • snapshot reference.

7. The Configuration Domain Model

A production-grade configuration model should be more structured than this:

config_key VARCHAR,
config_value VARCHAR

That key-value table is fine for low-risk settings. It is not enough for ERP behavior.

7.1 Conceptual Model

7.2 Example: Approval Limit Configuration

{
  "definitionCode": "PROCUREMENT_APPROVAL_LIMIT",
  "version": 12,
  "effectiveFrom": "2026-07-01T00:00:00+07:00",
  "status": "ACTIVE",
  "rules": [
    {
      "priority": 100,
      "scope": {
        "tenant": "T1",
        "legalEntity": "ID-PT-01",
        "department": "IT",
        "role": "PROCUREMENT_MANAGER"
      },
      "conditions": {
        "currency": "IDR"
      },
      "value": {
        "maxApprovalAmount": 100000000,
        "currency": "IDR",
        "requiresSecondApproverAbove": 75000000
      },
      "terminal": true
    },
    {
      "priority": 10,
      "scope": {
        "tenant": "T1",
        "legalEntity": "ID-PT-01"
      },
      "conditions": {
        "currency": "IDR"
      },
      "value": {
        "maxApprovalAmount": 25000000,
        "currency": "IDR"
      },
      "terminal": true
    }
  ]
}

The domain service should not parse this raw JSON directly. It should ask a typed resolver.


8. Scope Modelling

Scope is where most ERP configuration systems become messy.

A scope defines where a configuration applies.

Typical ERP scope dimensions:

DimensionExampleNotes
Tenanttenant=T1SaaS or hosted multi-tenant boundary
Legal entitycompany=ID-PT-01Legal/accounting boundary
Branch/sitebranch=JKTOperational boundary
Countrycountry=IDLocalization/tax/legal boundary
Departmentdepartment=ITWorkflow/cost control boundary
Cost centercostCenter=CC-1001Finance/control boundary
Warehousewarehouse=WH-JKT-01Inventory/fulfillment boundary
Document typedocumentType=POLifecycle/business policy boundary
Product categorycategory=RAW_MATERIALPricing/procurement/inventory boundary
Customer/vendor groupcustomerGroup=GOLDCommercial policy boundary
Channelchannel=ECOMMERCEOrder/pricing/fulfillment boundary
Rolerole=APPROVER_L2Approval/access behavior

8.1 Specificity Rules

When multiple rules match, we need deterministic precedence.

Example specificity order:

Tenant + Legal Entity + Branch + Department + Role
Tenant + Legal Entity + Department + Role
Tenant + Legal Entity + Role
Tenant + Legal Entity
Tenant
Global Default

This must be explicit. Never rely on accidental SQL ordering.

8.2 Conflict Example

Two approval rules match:

RuleScopePriorityLimit
AlegalEntity=ID-PT-01, department=IT100100M
BlegalEntity=ID-PT-01, role=MANAGER10075M

If user is IT manager, both match. If priority and specificity cannot break the tie, publication must fail.

Correct behavior:

Do not activate ambiguous config.

9. Effective Dating and Versioning

9.1 Why Effective Dating Exists

ERP behavior is time-bound.

Examples:

  • tax rate changes on a legal date;
  • approval limit changes next quarter;
  • fiscal period closes on a cutoff date;
  • warehouse negative-stock policy changes after stock audit;
  • bank file format changes from a future migration date;
  • country localization rule starts on regulatory enforcement date.

Config needs these fields:

valid_from
valid_to
published_at
activated_at
retired_at
transaction_effective_time_policy

These are not the same.

9.2 Transaction Effective Time

A document can have multiple relevant times:

TimeMeaningExample Use
Created timeWhen user created draftUI defaults
Submitted timeWhen document entered approvalapproval rule snapshot
Approved timeWhen authority approvedapproval evidence
Document dateBusiness date on documenttax/accounting rule
Posting dateLedger dateperiod lock/posting rule
Fulfillment dateGoods movement dateinventory valuation
Invoice dateLegal invoice datetax invoice numbering

A configuration definition should specify which time is used.

For example:

APPROVAL_LIMIT.evaluationTime = SUBMITTED_AT
TAX_RULE.evaluationTime = INVOICE_DATE
PERIOD_LOCK.evaluationTime = POSTING_DATE
PRICE_RULE.evaluationTime = ORDER_PRICED_AT

Without this rule, the same document can be evaluated inconsistently.

9.3 Snapshot vs Re-evaluation

Some configuration results should be snapshotted. Some can be re-evaluated.

Config TypeSnapshot?Reason
Tax calculation ruleYeslegal/financial evidence
Approval ruleYesaudit defensibility
Price calculationYescommercial commitment
Email templateUsually nolow-risk presentation
UI themeNopresentation only
Feature flagUsually norollout behavior
Posting period lockYes at decision momentcontrol evidence
Integration endpointUsually no for runtime, yes for sent message evidencesupport/replay

A posted or legally issued document should store enough metadata to explain its behavior later.


10. Lifecycle of Configuration

10.1 Draft

Draft allows authoring but must not affect production behavior.

10.2 Validated

Validation must check:

  • schema correctness;
  • value ranges;
  • unknown reference data;
  • invalid scope;
  • overlapping effective dates;
  • ambiguous matching;
  • missing fallback;
  • SoD violations;
  • unsafe change classification;
  • test scenario regression.

10.3 Approval Pending

High-risk config requires approval.

Examples:

  • tax rule;
  • approval matrix;
  • period lock override;
  • posting mapping;
  • legal numbering;
  • payment format;
  • integration route for financial transactions;
  • stock negative policy.

10.4 Published

Published means the config is ready but not necessarily active. A future-dated tax rule can be published today and become active later.

10.5 Active

Runtime resolvers can use active config.

10.6 Retired

Retired config is no longer used for new decisions but remains needed for historical explanation.

Do not delete retired business config if documents reference it.


11. Risk Classification

Not all configuration needs the same process.

RiskExampleRequired Control
LowUI label, email footersimple review
Mediumdefault payment term, default warehousevalidation + approval
Hightax rule, approval threshold, posting mappingmaker-checker + regression test + publication window
Criticallegal invoice numbering, period unlock, bank payment routeSoD + emergency log + privileged approval + monitoring

This classification should be part of CONFIG_DEFINITION.

Example:

{
  "definitionCode": "LEGAL_INVOICE_NUMBERING_POLICY",
  "riskLevel": "CRITICAL",
  "requiresApproval": true,
  "requiresSoD": true,
  "requiresRegressionPack": true,
  "requiresPublicationWindow": true,
  "defaultFailureMode": "BLOCK_OPERATION"
}

12. Technical Config vs Business Config

12.1 Technical Config

Technical config is about running the application.

Examples:

server:
  port: 8080

spring:
  datasource:
    url: jdbc:postgresql://erp-db:5432/erp
    username: ${ERP_DB_USERNAME}

posting:
  batch-size: 500
  worker-count: 8

This belongs in Spring Boot externalized configuration, Kubernetes config, secret manager, or equivalent platform mechanism.

12.2 Business Config

Business config is about changing business decisions.

Examples:

Purchase requisition above IDR 100M requires CFO approval.
Invoices cannot be posted to a closed accounting period.
Warehouse WH-JKT-01 does not allow negative stock.
A vendor invoice must be three-way matched before payment.

This belongs in an ERP domain module with:

  • schema;
  • version;
  • lifecycle;
  • audit;
  • approval;
  • validation;
  • runtime explanation.

12.3 The Boundary Rule

Use this rule:

If changing the value changes a business decision that must be explained to an auditor, customer, vendor, manager, tax authority, or internal control team, model it as business configuration, not technical configuration.


13. Java/Spring Implementation Pattern

13.1 Use Typed Technical Configuration

For technical values, prefer typed configuration classes.

@ConfigurationProperties(prefix = "posting")
public record PostingWorkerProperties(
        int batchSize,
        int workerCount,
        Duration leaseTimeout,
        Duration retryBackoff
) {
    public PostingWorkerProperties {
        if (batchSize <= 0 || batchSize > 10_000) {
            throw new IllegalArgumentException("posting.batch-size out of safe range");
        }
        if (workerCount <= 0 || workerCount > 128) {
            throw new IllegalArgumentException("posting.worker-count out of safe range");
        }
    }
}

This should not be used for tenant-specific approval rules or tax rules.

13.2 Use a Business Configuration Resolver

public interface BusinessConfigResolver {
    <T> ResolvedConfig<T> resolve(ConfigRequest<T> request);
}
public record ConfigRequest<T>(
        ConfigDefinitionCode definitionCode,
        Class<T> valueType,
        BusinessContext context,
        Instant evaluationTime,
        ConfigFailureMode failureMode
) {}
public record ResolvedConfig<T>(
        T value,
        String matchedRuleId,
        long version,
        ConfigScope matchedScope,
        Instant effectiveFrom,
        Instant effectiveTo,
        ConfigExplanation explanation
) {}

13.3 Example Usage in Domain Service

public PurchaseRequisition submit(SubmitPrCommand command) {
    PurchaseRequisition pr = repository.getForUpdate(command.prId());

    BusinessContext context = BusinessContext.builder()
            .tenantId(pr.tenantId())
            .legalEntityId(pr.legalEntityId())
            .departmentId(pr.departmentId())
            .documentType("PURCHASE_REQUISITION")
            .currency(pr.currency())
            .amount(pr.totalAmount())
            .requesterId(command.submittedBy())
            .build();

    ResolvedConfig<ApprovalPolicy> approvalPolicy = configResolver.resolve(
            new ConfigRequest<>(
                    ConfigDefinitionCode.of("PROCUREMENT_APPROVAL_POLICY"),
                    ApprovalPolicy.class,
                    context,
                    command.submittedAt(),
                    ConfigFailureMode.BLOCK_OPERATION
            )
    );

    pr.submit(command.submittedBy(), approvalPolicy.value());
    pr.attachDecisionEvidence(approvalPolicy.explanation().toEvidence());

    return repository.save(pr);
}

Notice the important part:

pr.attachDecisionEvidence(...)

Business decisions must carry evidence.


14. Database Schema Sketch

CREATE TABLE config_definition (
    code                    varchar(120) PRIMARY KEY,
    name                    varchar(240) NOT NULL,
    domain_code             varchar(80) NOT NULL,
    value_schema_json       jsonb NOT NULL,
    risk_level              varchar(30) NOT NULL,
    resolution_strategy     varchar(40) NOT NULL,
    evaluation_time_policy  varchar(60) NOT NULL,
    failure_mode            varchar(40) NOT NULL,
    requires_approval       boolean NOT NULL,
    requires_sod            boolean NOT NULL,
    created_at              timestamptz NOT NULL,
    created_by              varchar(120) NOT NULL
);

CREATE TABLE config_version (
    id                      uuid PRIMARY KEY,
    definition_code         varchar(120) NOT NULL REFERENCES config_definition(code),
    version_no              integer NOT NULL,
    lifecycle_status        varchar(40) NOT NULL,
    effective_from          timestamptz NOT NULL,
    effective_to            timestamptz,
    change_reason           text NOT NULL,
    created_at              timestamptz NOT NULL,
    created_by              varchar(120) NOT NULL,
    approved_at             timestamptz,
    approved_by             varchar(120),
    published_at            timestamptz,
    published_by            varchar(120),
    UNIQUE(definition_code, version_no)
);

CREATE TABLE config_rule (
    id                      uuid PRIMARY KEY,
    config_version_id       uuid NOT NULL REFERENCES config_version(id),
    priority                integer NOT NULL,
    terminal                boolean NOT NULL DEFAULT true,
    scope_json              jsonb NOT NULL,
    condition_json          jsonb NOT NULL,
    value_json              jsonb NOT NULL,
    explanation             text NOT NULL
);

CREATE TABLE config_resolution_audit (
    id                      uuid PRIMARY KEY,
    definition_code         varchar(120) NOT NULL,
    config_version_id       uuid NOT NULL,
    rule_id                 uuid NOT NULL,
    evaluated_at            timestamptz NOT NULL,
    evaluation_time         timestamptz NOT NULL,
    context_hash            varchar(128) NOT NULL,
    result_hash             varchar(128) NOT NULL,
    consumer_type           varchar(80) NOT NULL,
    consumer_id             varchar(120) NOT NULL
);

This is only a sketch. The exact schema depends on your database, query patterns, and governance requirements.


15. Resolution Algorithm

15.1 Basic Algorithm

1. Load active/published config versions for definition.
2. Filter versions by evaluation time.
3. Filter rules by scope compatibility.
4. Filter rules by conditions.
5. Sort by priority and specificity.
6. Detect ambiguity.
7. Return value and explanation.
8. Optionally persist resolution audit/snapshot.

15.2 Pseudocode

public <T> ResolvedConfig<T> resolve(ConfigRequest<T> request) {
    ConfigDefinition def = definitionRepository.get(request.definitionCode());

    List<ConfigVersion> candidates = versionRepository.findActiveAt(
            def.code(),
            request.evaluationTime()
    );

    if (candidates.isEmpty()) {
        return handleMissingConfig(request, def);
    }

    List<MatchedRule> matches = candidates.stream()
            .flatMap(version -> ruleRepository.findByVersion(version.id()).stream()
                    .map(rule -> matcher.match(version, rule, request.context())))
            .flatMap(Optional::stream)
            .filter(match -> conditionEvaluator.matches(match.rule(), request.context()))
            .sorted(ruleComparator())
            .toList();

    if (matches.isEmpty()) {
        return handleNoRuleMatched(request, def);
    }

    AmbiguityCheckResult ambiguity = ambiguityChecker.check(matches);
    if (ambiguity.isAmbiguous()) {
        throw new AmbiguousConfigurationException(ambiguity.message());
    }

    MatchedRule selected = matches.get(0);
    T value = valueMapper.convert(selected.rule().valueJson(), request.valueType());

    return explanationBuilder.build(request, selected, value);
}

15.3 Ambiguity Must Be a Publication Error

Runtime ambiguity means publication validation failed. Do not let production traffic discover ambiguous critical configuration.


16. Fallback Hierarchy

Fallback is necessary, but dangerous if hidden.

Example:

Specific branch policy
  -> legal entity policy
    -> tenant policy
      -> global policy

A config resolver should expose fallback path:

{
  "matched": "tenant=T1/legalEntity=ID-PT-01",
  "fallbacksConsidered": [
    "tenant=T1/legalEntity=ID-PT-01/branch=JKT",
    "tenant=T1/legalEntity=ID-PT-01",
    "tenant=T1",
    "global"
  ],
  "reason": "No branch-specific rule matched. Legal-entity rule selected."
}

Hidden fallback is a major support problem.


17. Runtime Caching

ERP config is read frequently. Naively querying rules for every line item can destroy performance.

But naive caching can destroy correctness.

17.1 Cache Levels

Cache LevelExampleRisk
In-memory per nodelocal Caffeine cachenode disagreement
Distributed cacheRedis/Hazelcaststale values, invalidation failure
Snapshot attached to documentpersisted decision evidencestorage overhead, stale if reused incorrectly
Compiled rule cacheprecompiled rule graphinvalidation complexity
Read modeldenormalized active config viewprojection lag

17.2 Safe Cache Key

The cache key must include enough resolution context.

Bad:

approval-policy:T1

Better:

approval-policy:T1:ID-PT-01:IT:PROCUREMENT_MANAGER:IDR:2026-07-01T00:00:00Z:version=12

For high-cardinality contexts, cache compiled rule sets rather than final results.

17.3 Cache Invalidation Event

{
  "eventType": "ConfigVersionPublished",
  "definitionCode": "PROCUREMENT_APPROVAL_POLICY",
  "versionNo": 12,
  "effectiveFrom": "2026-07-01T00:00:00+07:00",
  "publicationId": "CFG-PUB-2026-000813",
  "riskLevel": "HIGH"
}

All runtime nodes can use this event to invalidate or preload config.

17.4 Active-at-Time Query Problem

Future-dated config creates a common bug:

Cache is invalidated when config is published, but not when effective time arrives.

If a rule becomes active at midnight, the system needs either:

  • scheduled invalidation;
  • resolver query by evaluation time;
  • cache TTL shorter than effective boundary;
  • precomputed active window cache;
  • event emitted at activation time.

18. Configuration and Feature Flags

Feature flags are useful, but they are not a replacement for business configuration.

Feature FlagBusiness Configuration
controls rolloutcontrols business behavior
often temporaryoften durable
owned by product/platformowned by business/domain
can be percentage/user basedusually scope/effective-date based
may be removed after rolloutretained for evidence/history
frequently operationaloften auditable/legal/financial

Bad design:

if featureFlag("new-tax-rule") then apply tax 11% else 10%

Better design:

TaxRuleResolver resolves tax rule by jurisdiction, document date, product tax category, customer tax status, and published effective-dated rule version.

Feature flag may control whether the new engine is enabled, not the tax law itself.


19. Configuration and Secrets

Secrets are not business configuration.

Do not store these in business config tables:

  • bank API key;
  • tax service credential;
  • database password;
  • SFTP private key;
  • OAuth client secret;
  • signing certificate private key.

Business config may reference a secret alias:

{
  "bankConnector": "BANK_XYZ_HOST_TO_HOST",
  "credentialRef": "secret://erp/prod/bank-xyz/h2h-client"
}

The secret value itself belongs in a secret management system with rotation, access control, and audit.


20. Configuration as Data Product

Large ERP configuration should be treated like a data product.

It needs:

  • schema;
  • owner;
  • SLA;
  • quality checks;
  • publication channel;
  • lineage;
  • consumers;
  • backward compatibility;
  • change notification;
  • test scenarios;
  • operational dashboard.

Example ownership:

ConfigOwnerReviewerTechnical Steward
Approval matrixProcurement controlInternal auditWorkflow platform team
Tax ruleTax/legal teamFinance controllerCommercial rules team
Posting mappingAccounting policyFinance controllerLedger platform team
Warehouse policySupply chainInventory controllerWMS/ERP team
Bank routeTreasurySecurity + financeIntegration platform team

21. Validation Engine

A validation engine prevents invalid configuration from reaching runtime.

21.1 Validation Types

ValidationExample
Schema validationmaxApprovalAmount must be money object
Referential validationcost center exists and active
Scope validationbranch belongs to legal entity
Temporal validationno overlapping active versions unless strategy allows it
Conflict validationtwo rules cannot match same context with same priority
Completeness validationevery legal entity has period lock policy
Simulation validationsample documents produce expected decisions
Regression validationnew config does not break golden scenarios
SoD validationauthor cannot approve own high-risk config
Risk validationcritical config requires publication window

21.2 Simulation Test Example

{
  "scenario": "IT manager submits PR above 75M but below 100M",
  "context": {
    "tenant": "T1",
    "legalEntity": "ID-PT-01",
    "department": "IT",
    "role": "PROCUREMENT_MANAGER",
    "currency": "IDR",
    "amount": 90000000
  },
  "expected": {
    "requiresApproval": true,
    "requiredApproverLevel": "CFO",
    "matchedRuleId": "APR-LIMIT-2026-Q3-00042"
  }
}

Config publication should run these simulations.


22. Runtime Decision Evidence

ERP documents should capture configuration evidence for important decisions.

Example table:

CREATE TABLE document_decision_evidence (
    id                  uuid PRIMARY KEY,
    document_type       varchar(80) NOT NULL,
    document_id         uuid NOT NULL,
    decision_type       varchar(120) NOT NULL,
    config_definition   varchar(120) NOT NULL,
    config_version_id   uuid NOT NULL,
    config_rule_id      uuid NOT NULL,
    evaluated_at        timestamptz NOT NULL,
    evaluation_time     timestamptz NOT NULL,
    explanation_json    jsonb NOT NULL,
    result_hash         varchar(128) NOT NULL
);

This allows support and audit teams to answer:

Why did this purchase order require CFO approval?
Why did this invoice use this tax rate?
Why was this payment blocked?
Why did this item use average costing?
Why was this stock movement disallowed?

23. Configuration Package and Promotion

ERP configuration often moves across environments:

DEV -> SIT -> UAT -> PRE-PROD -> PROD

But business configuration is not always identical across environments.

23.1 Package Model

Configuration Package
├── metadata
├── definitions
├── versions
├── rules
├── reference mappings
├── validation scenarios
├── expected simulation outputs
├── migration notes
└── rollback plan

23.2 Promotion Controls

Before production activation:

  • validate schema;
  • compare against UAT-approved package;
  • verify reference data mapping;
  • run golden scenarios;
  • check SoD approval;
  • verify activation window;
  • notify consumers;
  • create rollback/suspend plan.

24. Configuration Change Categories

CategoryExampleDeployment Style
Additiveadd new branch-specific approval ruleregular publish
Correctivefix typo in non-critical labelfast publish
Behavioralchange invoice matching tolerancecontrolled release
Regulatorynew tax rate effective next monthfuture-dated publish
Emergencysuspend bank route due to outageemergency workflow
Breakingchange config schemaversioned migration

Breaking schema changes require special handling. Never assume all config consumers upgrade simultaneously.


25. Schema Evolution

Configuration value schema evolves.

Example v1:

{
  "maxApprovalAmount": 100000000,
  "currency": "IDR"
}

Example v2:

{
  "maxApprovalAmount": {
    "amount": 100000000,
    "currency": "IDR"
  },
  "requiresSecondApproverAbove": {
    "amount": 75000000,
    "currency": "IDR"
  }
}

A mature platform supports:

  • schema version;
  • backward-compatible readers;
  • migration scripts;
  • validation scenarios;
  • deprecation window;
  • consumer compatibility matrix.

Java implementation should avoid passing raw Map<String, Object> through the domain. Use typed records/classes at module boundary.


26. Multi-Tenant Configuration

Multi-tenant ERP needs strict isolation.

26.1 Tenant Default and Override

Global Default
  -> Tenant Default
    -> Legal Entity Override
      -> Branch Override
        -> Department/Role Override

26.2 SaaS Risk

For SaaS ERP, a config bug can impact many tenants.

Controls:

  • tenant-scoped publication;
  • canary tenant;
  • blast-radius limit;
  • tenant-level kill switch;
  • per-tenant config version pinning;
  • migration report;
  • tenant-specific audit export.

26.3 Managed Customization

Do not let every tenant fork the system. Prefer controlled variation through:

  • scoped config;
  • extension points;
  • metadata-driven forms;
  • localization packs;
  • rule packages;
  • integration adapters.

27. Example: Period Lock Configuration

Accounting period lock is high-risk.

27.1 Model

{
  "definitionCode": "ACCOUNTING_PERIOD_LOCK_POLICY",
  "riskLevel": "CRITICAL",
  "evaluationTimePolicy": "POSTING_DATE",
  "rules": [
    {
      "scope": {
        "tenant": "T1",
        "legalEntity": "ID-PT-01",
        "ledger": "PRIMARY"
      },
      "value": {
        "closedPeriods": ["2026-05"],
        "softClosedPeriods": ["2026-06"],
        "allowBackdatedPosting": false,
        "emergencyUnlockRequires": ["FINANCE_CONTROLLER", "INTERNAL_AUDIT"]
      }
    }
  ]
}

27.2 Runtime Behavior

27.3 Evidence

A posting allowed by emergency unlock must record:

  • unlock request;
  • approvers;
  • effective window;
  • reason;
  • affected period;
  • posting document;
  • audit flag;
  • reversal/adjustment reference if needed.

28. Example: Warehouse Negative Stock Policy

{
  "definitionCode": "NEGATIVE_STOCK_POLICY",
  "riskLevel": "HIGH",
  "evaluationTimePolicy": "MOVEMENT_TIME",
  "rules": [
    {
      "scope": {
        "tenant": "T1",
        "warehouse": "WH-JKT-01",
        "itemCategory": "RAW_MATERIAL"
      },
      "value": {
        "allowNegativeStock": false,
        "blockPickIfAvailableBelowZero": true,
        "allowSupervisorOverride": false
      }
    }
  ]
}

This config must be resolved inside the same logical transaction that validates stock movement. If resolved outside and stock changes before commit, the system can still oversell or over-issue.


29. Example: Integration Route Configuration

Integration config is partly business and partly technical.

{
  "definitionCode": "BANK_PAYMENT_ROUTE",
  "riskLevel": "CRITICAL",
  "rules": [
    {
      "scope": {
        "tenant": "T1",
        "legalEntity": "ID-PT-01",
        "currency": "IDR",
        "paymentMethod": "BANK_TRANSFER"
      },
      "value": {
        "connectorCode": "BANK_XYZ_H2H",
        "format": "BANK_XYZ_ISO20022_VARIANT_A",
        "credentialRef": "secret://erp/prod/bank-xyz/h2h-client",
        "requiresDualAuthorization": true,
        "maxBatchAmount": {
          "amount": 5000000000,
          "currency": "IDR"
        }
      }
    }
  ]
}

The connector endpoint may be technical config. The rule that selects this route for payments is business config.


30. Anti-Patterns

30.1 Universal Key-Value Table

config_key
config_value

This creates:

  • no type safety;
  • no lifecycle;
  • no audit explanation;
  • no scope model;
  • no conflict detection;
  • no simulation;
  • no ownership.

30.2 Business Logic Hidden in YAML

If finance rules are in application-prod.yml, business owners cannot govern them properly.

30.3 Config Without Version

Changing the value overwrites history. Audit cannot explain old decisions.

30.4 Runtime Editing Without Publication

A production admin changes behavior instantly with no validation. This may be convenient, but it is dangerous for ERP.

30.5 Over-Configurable Core

A system where everything is configurable becomes impossible to reason about.

A good ERP distinguishes:

configurable variation
custom extension
product feature
hard invariant

Not all business requests deserve runtime configuration.

30.6 Configuration as Programming Language

Some ERP platforms accidentally create a weak programming language inside configuration.

Symptoms:

  • nested expressions;
  • arbitrary scripts;
  • hidden side effects;
  • no test harness;
  • no debugging;
  • no static validation;
  • non-deterministic evaluation.

If you need complex computation, consider a rule engine or extension point with governance, not uncontrolled expression fields.


31. Failure Modes and Rescue Playbooks

FailureSymptomLikely CauseRescue
Wrong approval routePR went to wrong approverscope precedence bugsuspend config, replay workflow if safe, publish corrected version
Tax miscalculationinvoice tax wrongeffective date or jurisdiction rule errorblock affected invoice type, identify impacted docs, issue adjustment/credit process
Node disagreementusers see different behaviorstale local cachebroadcast invalidation, compare config version per node
Missing ruledocument cannot postincomplete configemergency fallback only if safe; otherwise block and publish fix
Unexpected fallbacktenant used global rulemissing tenant overrideadd validation completeness rule
Historical driftold document recalculates differentlyno snapshotstop recalculation, add evidence snapshot going forward, reconcile past docs
Rule conflictpublication fails or runtime ambiguousoverlapping rulesimprove specificity/priority model
Emergency change abusefrequent manual overridesweak governanceadd dashboard, approval threshold, post-incident review

32. Testing Strategy

32.1 Unit Tests

Test matcher, specificity, date range, and value mapping.

@Test
void branchSpecificRuleBeatsLegalEntityDefault() {
    var result = resolver.resolve(requestFor("WH-JKT-01"));
    assertThat(result.matchedScope().warehouseId()).isEqualTo("WH-JKT-01");
}

32.2 Property Tests

Useful properties:

  • no two active rules produce ambiguous result for generated contexts;
  • every legal entity has a period lock policy;
  • every active tax jurisdiction has at least one active tax rule;
  • effective date windows do not overlap for terminal rules;
  • resolution is deterministic.

32.3 Golden Scenario Tests

Maintain a catalog of business scenarios:

  • PR below threshold;
  • PR above threshold;
  • invoice in closed period;
  • invoice in soft-closed period;
  • warehouse transfer with negative stock disallowed;
  • payment route over max batch amount;
  • tax exempt customer;
  • cross-border sale;
  • branch override;
  • country localization pack.

Each publication should run relevant golden scenarios.

32.4 Production Shadow Evaluation

For high-risk rollout, evaluate new config in shadow mode:

current active result = A
future candidate result = B
compare A vs B
record difference
surface expected impact before activation

This is powerful for approval matrix, pricing, tax, and routing changes.


33. Observability

A configuration platform needs operational visibility.

33.1 Metrics

  • config resolution count by definition;
  • cache hit/miss;
  • resolution latency;
  • missing config count;
  • fallback usage count;
  • ambiguous config detection;
  • publication failure count;
  • emergency change count;
  • stale node version count;
  • shadow evaluation mismatch count.

33.2 Logs

High-risk decisions should log structured evidence identifiers.

{
  "event": "CONFIG_RESOLVED",
  "definitionCode": "PROCUREMENT_APPROVAL_POLICY",
  "version": 12,
  "ruleId": "APR-LIMIT-2026-Q3-00042",
  "consumerType": "PURCHASE_REQUISITION",
  "consumerId": "PR-2026-000081",
  "tenant": "T1",
  "legalEntity": "ID-PT-01"
}

33.3 Admin Dashboard

Useful views:

  • active config by definition/scope;
  • upcoming effective changes;
  • recently published high-risk config;
  • emergency changes;
  • documents using a config version;
  • nodes running stale config;
  • validation failures;
  • orphaned references;
  • fallback usage hotspots.

34. Design Review Checklist

Use this checklist during architecture review.

34.1 Classification

  • Is this technical config or business config?
  • Does changing it affect legal, financial, operational, or audit behavior?
  • Is it master data, reference data, policy, rule, feature flag, or secret?

34.2 Scope

  • What dimensions does it apply to?
  • Is scope hierarchy explicit?
  • Can two rules match the same context?
  • Is conflict detection implemented before activation?

34.3 Time

  • What evaluation time is used?
  • Is historical behavior preserved?
  • Are future-dated changes supported?
  • What happens at effective-time boundary?

34.4 Lifecycle

  • Who can author, approve, publish, suspend, and retire?
  • Is SoD enforced?
  • Are high-risk changes tested before activation?
  • Is emergency change controlled?

34.5 Runtime

  • Is resolution deterministic?
  • Is failure mode explicit?
  • Is cache invalidation safe?
  • Is decision evidence captured?

34.6 Operations

  • Can support answer “why did this happen?”
  • Can we find all documents impacted by a config version?
  • Can we safely roll forward or suspend?
  • Are validation and publication failures observable?

35. Practice Drill

Scenario

Your ERP supports procurement across three legal entities:

  • Indonesia entity;
  • Singapore entity;
  • Australia entity.

The business wants configurable approval limits by:

  • legal entity;
  • department;
  • role;
  • currency;
  • purchase category;
  • effective date.

They also want emergency temporary limits during quarterly budget freeze.

Task

Design:

  1. configuration definition;
  2. scope hierarchy;
  3. value schema;
  4. lifecycle states;
  5. conflict detection rules;
  6. example runtime resolver request;
  7. decision evidence stored on purchase requisition;
  8. simulation test cases;
  9. operational dashboard metrics;
  10. emergency override process.

Expected Mental Model

A strong design will treat approval limits as:

high-risk, effective-dated, scoped, versioned, governed business configuration

not as:

approval.limit.idr=100000000

36. Source Notes

This part is grounded in widely used enterprise Java and integration/configuration references:

  • Spring Boot Externalized Configuration: useful for technical/environment configuration, not a complete ERP business configuration model.
  • MicroProfile Config 3.1: defines a portable configuration system and configurable sources for Java microservices.
  • Jakarta Persistence 3.2: relevant when modelling governed configuration as durable domain data.
  • OWASP logging and verification guidance: relevant to audit evidence and control-oriented logging.
  • ERP practice from financial, supply-chain, procurement, and manufacturing domains where effective-dated, scoped, approved, and auditable behavior is required.

37. Key Takeaways

  • Configuration in ERP is not just values; it is controlled runtime behavior.
  • High-risk business configuration needs scope, time, version, lifecycle, validation, approval, and evidence.
  • Technical config belongs in platform mechanisms; business config belongs in governed domain modules.
  • Every important decision should be explainable by config version, rule, scope, and evaluation time.
  • Caching must not break correctness at effective-time boundaries.
  • A mature ERP treats configuration as a product with owners, tests, publication workflow, and observability.
Lesson Recap

You just completed lesson 21 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.