Why business rules buried in microservices code create tightly-coupled monoliths anyway
Last quarter I sat with the Lead Architect at a $1.7B GWP P&C carrier whose engineering team had spent 18 months migrating from a monolithic policy admin system to an event-driven microservices architecture on Kubernetes. The migration was technically successful - 47 microservices in production, Kafka backbone, Istio service mesh, the modern shape. The problem was that underwriting eligibility rules had been replicated across 6 different microservices. Quote service, bind service, endorsement service, renewal service, and two pricing services all had their own copy of the same 40 rules. When the Texas credit-score threshold changed, the engineering team had to coordinate 6 simultaneous code releases. "We have a distributed monolith," the architect told me. "All the operational cost of microservices, none of the agility benefit."
In my experience, this is the most common microservices anti-pattern in insurance carriers. Business rules embedded in service code re-coupled what the architecture intentionally decoupled. The fix is structural - extract rule evaluation into a Decision-as-a-Service component that all microservices call, version the rules independently, and treat decisions as a domain concept separate from the services that consume them.
This article covers how to integrate a business rules engine into a microservices architecture without recreating the coupling problem. I cover the Decision-as-a-Service pattern, three integration patterns (REST synchronous, Kafka event-driven, embedded SDK), API Gateway and Istio service mesh patterns, sizing for production throughput, a concrete insurance carrier architecture, and where I tell prospects this entire approach is the wrong fit. Skip to Section 4 if you want the architecture diagram; Section 6 for the Kafka pattern with code; Section 9 is where I admit when monolithic deployment beats microservices.
This is a technical reference for Daniel-architect persona at mid-market insurance carriers and banks - not a sales pitch. Concrete artifacts (Kafka adapter code, Kubernetes manifests, sizing tables) are included to be useful.
The coupling problem and what Decision-as-a-Service actually means
Microservices architecture promises that each service can be developed, deployed, and scaled independently. Business rules embedded in service code break that promise quietly. The eligibility logic that lives in quote-service must also live in bind-service (because bind validates eligibility too), in renewal-service, in endorsement-service. Every change to eligibility logic requires synchronized code changes across N services, synchronized deployments, and synchronized rollback if anything goes wrong. The result: distributed monolith with all the operational cost of microservices and none of the agility.
Decision-as-a-Service is the pattern that fixes this. Three principles:
- Rules are a domain concept, not a service implementation detail. Underwriting eligibility belongs to the underwriting domain, not to whichever microservice happens to need it first. Modeling rules as a domain artifact (decision tables, decision flows) lets multiple services consume the same decision logic without each owning its own copy.
- Rule storage is centralized, rule evaluation can be distributed. The rule definitions live in one source-of-truth database that the rules engine reads. Rule evaluation can run as embedded SDK in some services and as REST calls from others - both modes read from the same rule store, so authoring changes propagate everywhere consistently.
- Rule changes deploy independently of service code. Business analysts edit decision tables in the rule engine's authoring tool (Higson Studio for the Linda BA persona); the change deploys to the rules engine within minutes; all consuming microservices see the updated rule on their next refresh cycle. No service code release required.
The trade-off is real. Centralizing rule logic adds a network hop for microservices that previously had the rule inline. In my experience that network hop costs 1-3 ms at typical cluster topology - acceptable for almost all mid-market workloads but worth measuring for ultra-low-latency paths. For paths where the network hop is unacceptable, the embedded SDK pattern (rule evaluation in-process, rule storage centralized) keeps the decoupling without the latency cost.
Reference architecture: BRMS in a microservices stack
The architecture I recommend for mid-market insurance carriers running microservices on Kubernetes. Three layers separated by clear boundaries.
[API Gateway: Kong / Istio Gateway]
|
+-------------+-------------+
| | |
[quote-svc] [bind-svc] [endorsement-svc]
| | |
+-------------+-------------+
|
[Higson Runtime REST]
|
+-------------+-------------+
| |
[PostgreSQL] [Kafka cluster]
(rule store) (event stream)
^ |
| v
[Higson Studio] [BRMS event adapter]
(Linda BA author) |
v
[downstream services]Three observations about this shape:
- Higson Runtime REST is the single decision authority. Every microservice that needs to evaluate underwriting eligibility, pricing factors, claims routing, or any other rule-based decision calls Higson Runtime REST. The microservices do not own decision logic; they own their domain workflow and delegate decisions out.
- Higson Studio sits outside the production data plane. Linda the BA authors decision tables in Studio, which writes to the PostgreSQL rule store. Runtime REST reads from the same store on a refresh cycle (default 30 seconds, configurable). Authoring traffic never touches production decision traffic.
- Kafka adapter handles event-driven decisions. For services that consume events rather than synchronous API calls, a small BRMS event adapter consumes the relevant Kafka topic, calls Runtime REST synchronously, and publishes decision results back to downstream topics. The adapter is small and stays out of the main decision path.
Three integration patterns: REST, Kafka, and embedded SDK
Most production deployments mix all three. Pick the right pattern per service based on latency requirements and stack.
Pattern 1: Synchronous REST (the default)
Most calling services use REST. The microservice receives a request, calls Higson Runtime REST to evaluate the relevant decision, receives the result, continues processing. Typical end-to-end latency: 1-3 ms including network hop, with rule execution itself at 0.23 ms P50.
// Example: quote-svc evaluating underwriting eligibility
POST /api/v1/rules/auto-eligibility/execute
Content-Type: application/json
{
"input": {
"creditScore": 720,
"state": "TX",
"priorClaims36mo": 0,
"mvrViolations": 0
}
}
// Response includes decision + rule version + audit ID
{
"decision": "approve",
"tier": "preferred",
"ruleVersion": "2026.05.18-1234",
"auditLogId": "a7c3..."
} When to use: standard CRUD-style microservice flows, quote-to-bind paths, claims FNOL processing, anywhere the calling service is synchronous and the latency budget allows 1-3 ms network hop. This is 70-80% of production decision traffic in my experience.
Pattern 2: Event-driven via Kafka
For event-driven services that consume Kafka topics, the pattern I recommend is a small BRMS event adapter that bridges Kafka and the REST API. The adapter consumes the relevant topic, calls Runtime REST synchronously, and publishes the decision result back to a downstream topic for downstream consumers to act on.
// Pseudo-code: BRMS event adapter (Kafka -> Higson Runtime REST -> Kafka)
@KafkaListener(topics = "quote-submitted")
public void onQuoteSubmitted(QuoteEvent event) {
// Build decision input from event
var input = Map.of(
"creditScore", event.getCreditScore(),
"state", event.getState(),
"priorClaims36mo", event.getPriorClaims(),
"mvrViolations", event.getMvr()
);
// Call Higson Runtime REST
var decision = higsonClient
.rule("auto-eligibility")
.input(input)
.execute();
// Publish decision to downstream topic
var decisionEvent = new DecisionEvent(
event.getQuoteId(),
decision.getDecision(),
decision.getTier(),
decision.getAuditLogId()
);
kafkaTemplate.send("quote-decisioned", decisionEvent);
}When to use: event-sourced architectures, async claims processing pipelines, batch enrichment flows, anywhere services communicate through Kafka rather than synchronous HTTP. The adapter pattern keeps the decisioning boundary clean - Higson does not need to know about Kafka, and the event-streaming logic stays separate from rule logic.
I recommend running the adapter as a separate microservice rather than embedding Kafka consumption inside Runtime REST. This keeps Runtime REST stateless and lets the adapter scale independently based on Kafka throughput.
Pattern 3: Embedded Java SDK (lowest latency)
For Java microservices on the hot path - real-time pricing, point-of-sale fraud scoring, in-flight quote evaluation - the network hop to Runtime REST is sometimes unacceptable. Higson ships as a Maven/Gradle dependency that runs rule evaluation in-process while reading the same rule store the REST mode uses.
// In-process evaluation - no network hop
HigsonClient client = HigsonClient.create(
HigsonConfig.builder()
.dbUrl("jdbc:postgresql://higson-db:5432/rules")
.ruleRefreshInterval(Duration.ofSeconds(30))
.build()
);
DecisionResult result = client
.rule("auto-eligibility")
.input(inputMap)
.execute(); // ~0.23 ms P50, no networkWhen to use: highest-throughput Java microservices where 1-3 ms network hop matters. Embedded SDK reads from the same rule store as Runtime REST, so authoring changes propagate identically. The trade-off: you give up some architectural cleanliness (now the calling service holds the SDK), but you get sub-millisecond decision latency in-process.
API Gateway and ingress patterns for the BRMS
Higson Runtime REST sits behind whatever API Gateway your cluster uses - Kong, Ambassador, Istio Gateway, AWS ALB. Two configuration concerns matter for production.
Internal-only by default
Higson Runtime REST should not be reachable from the public internet. The decision API is consumed by your own microservices inside the cluster, not by external clients. I recommend:
- Cluster-internal service exposure only (ClusterIP service, no LoadBalancer)
- NetworkPolicies restricting which namespaces can reach Higson Runtime REST
- If exposure outside the cluster is required (e.g., for partner integrations), route through API Gateway with explicit auth (OIDC, mTLS)
Rate limiting and circuit breaking
Even internal services need protection. The Istio sidecar (or Kong plugin) should enforce:
- Per-namespace rate limits to prevent one runaway microservice from overwhelming the BRMS
- Circuit breaker at consuming service side - if Higson Runtime REST is degraded, the calling service should fail fast with a known fallback (e.g., "refer to manual underwriting") rather than time out
- Retry with exponential backoff for transient failures, capped at 3 attempts
The fallback pattern is the one I have seen most often missed. When the rules engine is briefly unavailable - planned upgrade, transient network issue, database failover - the calling service must have a defined fallback behavior. "Refer to human review" is a safer default than "approve everything" or "decline everything." I recommend documenting the fallback behavior per service explicitly during the integration design phase, not after the first production incident.
Istio service mesh integration
For carriers running Istio as the service mesh, three integration concerns deserve explicit attention.
mTLS for service-to-service traffic
Istio's default mTLS PERMISSIVE mode lets the BRMS accept both plain HTTP and mTLS traffic during migration. For production I recommend STRICT mTLS once all consuming services are migrated - this enforces cryptographic identity on every call to Runtime REST and gives auditors clean evidence that internal traffic is authenticated and encrypted.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: higson-mtls
namespace: higson
spec:
mtls:
mode: STRICTAuthorization policies
Istio AuthorizationPolicy lets you define which service identities can call Higson Runtime REST. The principle of least access: only the namespaces and service accounts that genuinely need decision evaluation should be allowed to call.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: higson-runtime-access
namespace: higson
spec:
selector:
matchLabels:
app: higson-runtime-rest
rules:
- from:
- source:
namespaces: ["quote", "bind", "claims", "pricing"]
to:
- operation:
methods: ["POST"]
paths: ["/api/v1/rules/*/execute"]Observability via Istio telemetry
Istio's built-in Prometheus metrics give you decision-traffic visibility for free: P50/P95/P99 latency per source namespace, error rate, request volume. I recommend setting alerts on:
- P99 latency from any consuming namespace exceeding 50 ms (rule execution + network hop)
- Error rate per consuming namespace exceeding 0.5%
- Request rate per consuming namespace exceeding the per-namespace rate limit (signals runaway service)
Statelessness, horizontal scaling, and sizing
Higson Runtime REST is stateless by design - the state lives in the PostgreSQL rule store. This makes horizontal scaling clean: add more Runtime REST replicas behind the Kubernetes service, the cluster load balances automatically, no session affinity required.
Horizontal Pod Autoscaler configuration
I recommend HPA based on both CPU and custom metrics (request rate). CPU-only HPA misses bursty traffic patterns common in insurance (Monday morning policy renewals, end-of-month claims processing).
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: higson-runtime-hpa
namespace: higson
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: higson-runtime-rest
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: "1500"Database connection pool sizing
Common scaling trap: scale Higson Runtime REST horizontally without scaling database connections. If you run 10 replicas with default connection pool of 20 each, you need 200 PostgreSQL connections available. The fix is either tighter per-pod pools or PgBouncer in front of PostgreSQL. I recommend PgBouncer at 3+ Runtime replicas - it pools connections cleanly and gives you a single tuning point.
Rule refresh interval trade-off
Higson Runtime REST polls the rule store on a refresh interval (default 30 seconds, configurable down to 5 seconds). Shorter interval means faster authoring-change propagation but higher database read load. For most mid-market carriers, 30 seconds is fine - rule changes do not need second-by-second propagation. For high-frequency-trading-style scenarios where rules change throughout the day, 5-10 seconds may be warranted.
Real architecture: $1.7B GWP carrier on Kubernetes (anonymized)
Concrete architecture from the Mid-Atlantic carrier I mentioned at the top of this article. Names changed; the architecture is real.
Stack: AWS EKS (Kubernetes 1.29), Istio 1.20, Kafka MSK, PostgreSQL RDS Multi-AZ, 47 microservices across underwriting, pricing, claims, billing, document, and integration domains. After the BRMS integration:
- Higson deployment: 5 Runtime REST replicas (HPA 3-12 based on req/s), 2 Higson Studio replicas, dedicated higson namespace with STRICT mTLS, PostgreSQL RDS db.m5.xlarge Multi-AZ with PgBouncer in front, rule store size ~12 GB including 14 months of audit history.
- Consuming microservices: 23 of the 47 microservices call Higson - all the underwriting, pricing, claims-routing, billing-fee-calculation, and document-generation services. The other 24 are pure infrastructure (auth, logging, search, etc.) and do not need decision evaluation.
- Integration mix: 18 services use synchronous REST (quote, bind, endorsement paths). 3 services use Kafka adapter (claims event stream, fraud scoring stream, renewal batch). 2 services use embedded Java SDK (real-time pricing, point-of-sale fraud scoring - latency-sensitive paths).
- Decision volume: Sustained 4 500 req/s P95 with peaks to 7 200 req/s during Monday-morning renewal batch. Higson sustains 0.23 ms P50, 2.1 ms P99 at this load with the 5 replicas.
- Rule library: 7 800 rules across 22 decision tables. Linda the BA owns the rule library; Daniel's architecture team owns the integration. Rule changes deploy from Studio to production in 90 seconds (refresh interval + propagation). Compare to the 4-week sprint cycles before the BRMS integration.
The before-and-after that matters: rule changes that previously required coordinated code releases across 6 microservices now require zero code release. Linda authors the change in Studio, runs UAT against historical quotes, deploys to production. The 47-microservice stack stays as decoupled as the architecture promised.
When microservices integration of a BRMS is the wrong answer
I would rather lose a deal than win one badly. Three scenarios where I tell prospects that integrating a BRMS into their microservices architecture is not the right call - sometimes Higson is not the fit, sometimes microservices itself is not the fit:
- Sub-100-rule systems with low change velocity. If your business has 30-80 rules total, changing every quarter or less, the operational overhead of running a separate BRMS infrastructure does not pay back. Keep the rules in a documented spreadsheet or a small in-service library; spend the engineering effort elsewhere. The BRMS pattern earns its complexity at 500+ rules and weekly-or-faster change velocity.
- Stacks that are not actually microservices. I have watched carriers describe their architecture as 'microservices' that are really a distributed monolith - 8 services that all share the same database, deploy together, and have no actual service boundaries. Integrating a BRMS into that shape does not fix the architecture problem; it adds another component to maintain. Fix the service boundary problem first.
- Ultra-low-latency paths beyond 0.5 ms P99 SLA. Higson Runtime REST sits at ~1-3 ms end-to-end including network hop. The embedded Java SDK runs at 0.23 ms P50 in-process. If your SLA is sub-0.5 ms P99 (real-time bidding, high-frequency trading), you may need to skip the network hop entirely and design rule evaluation as inline code with strict change control. This is rare in insurance but real in capital markets and ad-tech.
Within mid-market insurance, banking, and healthcare with 1 000+ rules and quarterly-or-faster change velocity, the BRMS-in-microservices pattern works well. Outside that profile, an honest evaluation acknowledges where the pattern runs out of road.
FAQ
How do you integrate a business rules engine into a microservices architecture?
Three integration patterns, usually mixed in production: (1) Synchronous REST - microservice calls Higson Runtime REST, receives decision in 1-3 ms end-to-end including network hop. Use for 70-80% of decision traffic. (2) Kafka event-driven - small BRMS event adapter consumes Kafka topic, calls Runtime REST, publishes decision back to downstream topic. Use for event-sourced architectures. (3) Embedded Java SDK - rule evaluation in-process at 0.23 ms P50, reads same rule store as REST mode. Use for latency-sensitive paths where 1-3 ms network hop is unacceptable.
What is the Decision-as-a-Service pattern?
Decision-as-a-Service is the architectural pattern where business rules are centralized in a dedicated rules engine that all microservices call rather than embedding rule logic inline in each service. Three principles: rules are a domain concept (not service implementation detail), rule storage is centralized while evaluation can be distributed (REST + embedded SDK both read same rule store), rule changes deploy independently of service code (business analyst edits in Studio, change propagates to all services without code release). This fixes the distributed monolith anti-pattern where rule changes require coordinated code releases across N services.
What is the architecture of a business rules engine in microservices?
Standard architecture has three layers separated by clear boundaries. API Gateway (Kong/Istio Gateway) handles external traffic. Microservices (quote, bind, claims, pricing, etc.) call Higson Runtime REST for decision evaluation. Higson Runtime REST is stateless and scales horizontally. PostgreSQL rule store holds rule definitions, version history, and audit log. Higson Studio provides no-code authoring for Business Analysts, writes to the same rule store. Optional Kafka adapter bridges event-streaming services to the REST API. The full stack runs in a dedicated namespace with NetworkPolicies and Istio AuthorizationPolicy restricting access.
How does a rules engine work with Kafka in event-driven architecture?
Use a small BRMS event adapter as a separate microservice that bridges Kafka and the REST API. The adapter consumes the relevant Kafka topic (e.g., quote-submitted), calls Higson Runtime REST synchronously to evaluate the decision, then publishes the decision result back to a downstream topic (e.g., quote-decisioned) for downstream consumers. This keeps the decisioning boundary clean - Higson does not need to know about Kafka, and event-streaming logic stays separate from rule logic. The adapter scales independently based on Kafka throughput, runs as its own pod with Kafka consumer libraries.
What is the latency of a rules engine REST API call?
For Higson Runtime REST at typical Kubernetes cluster topology: 1-3 ms end-to-end per call, with rule execution itself at 0.23 ms P50 and 1.5 ms P99. The remaining latency is network hop, serialization, and Istio sidecar overhead. For paths where 1-3 ms is unacceptable, the embedded Java SDK runs rule evaluation in-process at 0.23 ms P50 - same rule store, same authoring path, no network hop. Mid-market carriers typically use REST for 70-80% of services and embedded SDK only on latency-sensitive paths like real-time pricing or point-of-sale fraud scoring.
How do you scale a rules engine horizontally?
Higson Runtime REST is stateless - state lives in the PostgreSQL rule store. Scale horizontally with Kubernetes HPA based on CPU utilization AND custom request-rate metric (CPU-only misses bursty traffic). Typical mid-market deployment: 3-12 replicas, 2-4 Gi memory each, target 1 500 req/s per replica before scale-up. Database connection pool sizing is the usual constraint at scale: 10 replicas × 20 connections = 200 PostgreSQL connections. Add PgBouncer in front of PostgreSQL at 3+ replicas - pools connections cleanly and gives a single tuning point.
What happens if the rules engine becomes unavailable?
Calling microservices need defined fallback behavior, not just retry-with-backoff. Common patterns: (1) Refer to human review - default for underwriting and claims paths; safer than auto-approving or auto-declining; (2) Cache last-known decision - use Redis or in-memory cache to serve recent decisions for short windows; acceptable for low-stakes decisions but not for time-sensitive ones; (3) Fail closed (decline) - safest for fraud-detection and high-risk paths but creates customer experience cost; (4) Fail open (approve) - rarely acceptable; only for low-stakes operational decisions with clear audit trail. Define the fallback behavior per service explicitly during integration design phase, not after the first production incident.
When is microservices integration of a BRMS the wrong choice?
Three scenarios where the pattern is the wrong fit: (1) Sub-100-rule systems with low change velocity - BRMS operational overhead does not pay back; keep rules in documented spreadsheet or small in-service library. (2) Stacks that are not actually microservices - distributed monoliths where services share databases, deploy together, and have no real service boundaries; fix the architecture problem first. (3) Ultra-low-latency paths beyond 0.5 ms P99 SLA - rare in insurance but real in capital markets and ad-tech; may need inline code with strict change control rather than network-hop BRMS calls.
Related reading
- What is a Rules Engine? Complete Guide for Insurance 2026 - the BRMS primer including microservices context.
- Higson Deployment Guide: Kubernetes, AWS, REST API - architecture, manifests, sizing for production.
- Java Rules Engines: Drools, Easy Rules, Higson SDK Comparison - Java SDK reference for embedded mode.
- Scalability in Business Rules Engines - sizing patterns for thousands of rules.
- Building Your Own Rules Engine: Build vs Buy TCO - what you would have to build to do this yourself.
- Higson is Secure - mTLS, encryption, audit trail for production deployment
Talk to Higson
The microservices-plus-BRMS pattern is where most insurance carriers either accelerate their architecture promise or quietly recreate the monolith they migrated away from. In my experience, getting this integration right in the first 6 months sets the trajectory for the next 5 years - distributed monolith carriers find themselves coordinating multi-service code releases again by year 2; properly-decoupled carriers compound agility benefits as rule libraries grow.
Higson is built for mid-market insurance carriers $500M-$5B GWP, mid-market banks $1B-$20B AUM, and mid-size healthcare payers running modern Kubernetes-based microservices stacks. We are not the right answer for sub-100-rule systems with low change velocity (BRMS overhead does not pay back), distributed monoliths masquerading as microservices (fix the architecture first), or ultra-low-latency paths beyond 0.5 ms P99 SLA (rare in insurance). Where we do fit, customers move from initial PoC to production deployment in 3-6 months with Kubernetes manifests, Istio AuthorizationPolicy, and HPA configuration that match their existing infrastructure.
If you would like to see Higson Runtime REST in your Kubernetes cluster - the manifests, the Kafka adapter pattern, the sizing for your actual throughput - I would be happy to walk through it with your architect team.
Three ways to start:
- Try Higson on AWS Marketplace at $0.63 / hour - 15 minutes from subscription to first decision execution against a sample insurance rule set.
- Schedule a 30-minute architecture review - we will walk through your specific microservices stack, sizing requirements, and integration patterns.
- Download the BRE Comparison Guide - 12 vendors compared on integration patterns, deployment options, and architecture.
Citations
- Sam Newman, "Building Microservices" (O'Reilly, 2nd Edition) - foundational microservices architecture text covering decoupling principles.
- Martin Fowler, "Microservices" - bliki essay on microservices architecture trade-offs. https://martinfowler.com/articles/microservices.html
- Kubernetes documentation - Deployment, Service, HPA, NetworkPolicy reference. https://kubernetes.io/docs/
- Istio documentation - PeerAuthentication, AuthorizationPolicy, telemetry reference. https://istio.io/latest/docs/
- Apache Kafka documentation - producer, consumer, exactly-once semantics. https://kafka.apache.org/documentation/
- Spring Boot Actuator documentation - health, metrics, prometheus endpoints used in Higson observability setup. https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/
- NAIC Model Bulletin on the Use of Artificial Intelligence Systems by Insurers (2023, updated 2024-2025) - audit-trail requirements satisfied by centralized BRMS logging. https://content.naic.org/sites/default/files/inline-files/2023-12-4 Model Bulletin_Adopted_0.pdf
- OMG Decision Model and Notation (DMN) Specification - rule format Higson implements. https://www.omg.org/dmn/
- Notus Finance / Higson case study (Drools migration, 100 000 calculations in 8 seconds) - https://www.higson.io/case-study/

Take Full Control of Your Product Logic
We provide fee Proof Of Concept, so you can see how Higson can work with your individual business logic.





