Engineering hubs in Dehradun & Bengaluru · Delivering across 10 countries

nitesh@redcubical.com +91 90687 14658

REDCUBICALSYSTEMS

Services / Integrate

Service architecture and integration, sized to the problem you have

We design service boundaries from your domain, connect systems with event-driven messaging that survives partial failure, and build APIs your consumers can depend on. That includes saying no to microservices when a modular monolith is the correct answer, which is more often than the industry admits.

  • Modular monolith first unless you can name the constraint microservices solve
  • Boundaries from event storming with your domain experts, not from the org chart
  • At-least-once delivery with idempotent consumers and the outbox pattern
  • Consumer-driven contract tests so a deploy cannot silently break a caller

At a glance

Messaging
Kafka, MSK, SQS, SNS, EventBridge, RabbitMQ, Kinesis
API styles
REST, GraphQL, gRPC, webhooks, async events
Gateways
Amazon API Gateway, Kong, Apigee, Azure API Management
Contract testing
Pact, OpenAPI and Protobuf schema checks
Legacy protocols
SOAP, WSDL, IBM MQ, SFTP flat file, ODBC
Availability target
99.95% on supported architectures
Engineers
40+ across 10 delivery markets

Start here

When not to do microservices

This is the most consequential decision on this page and the one most often made for the wrong reasons. Read it before the rest.

Should you do microservices

Do not adopt microservices unless you can name the specific constraint they remove. Microservices solve an organisational problem: too many engineers contending over one deployment unit. They do not make code cleaner, faster or more reliable. Below roughly thirty engineers, a modular monolith gives you the boundaries without the distributed-systems bill.

Reasons that are not good enough

  • "Our codebase is a mess." Distribution does not impose design discipline. It converts a tangled codebase into a tangled network of services, where the same coupling now fails intermittently and is harder to trace.
  • "We need to scale." Almost always you need to scale one component. Extract that one. A single well-tuned monolith on modern hardware handles far more traffic than most teams assume.
  • "We want to use different languages." Real, but expensive. Every language adds a build chain, dependency policy, security scanning path and hiring requirement. Justify it per service, not as a principle.
  • "It is the modern architecture." It is a fifteen-year-old trade-off with well-documented failure modes. The companies most associated with it also have platform teams larger than your entire engineering organisation.

Reasons that justify it

  • Deployment contention you can measure. Release trains delayed by unrelated changes, and a change failure rate driven by coupling between teams rather than by defects.
  • Genuinely divergent scaling profiles. A component needing GPUs, or one hundred times the throughput of the rest, or a completely different burst pattern.
  • Independent availability requirements. A payment path that must remain up while a reporting subsystem is down, with that isolation contractually required.
  • Regulatory or data-residency isolation. A bounded context that must run in a specific jurisdiction or under separate access control and audit.
  • Organisational reality. Multiple autonomous teams with distinct roadmaps, product ownership and on-call rotations, each able to own a service properly.
Modular monolith versus microservices
DimensionModular monolithMicroservices
TransactionsOne database transaction across modules. Correctness is cheapDistributed. Sagas, compensations and eventual consistency visible to users
DebuggingA single stack trace covers the whole requestDistributed tracing required. Without it, root cause analysis is guesswork
Local developmentClone, run, workMany services or a maintained set of fakes. Onboarding cost rises sharply
DeploymentOne artefact. Simple, and a bottleneck once many teams contend for itIndependent per service. The main genuine benefit
Failure modesMostly total. The process is up or downMostly partial, which is harder. Timeouts, retry storms, cascading saturation
Team size where it fitsUp to roughly thirty engineers on one productMultiple autonomous teams, typically thirty engineers upward
Cost of getting it wrongModules blur into a tangle. Recoverable with refactoringA distributed tangle. Recovery means re-merging services, which is far harder

The pragmatic path we recommend most often: build a modular monolith with strictly enforced module boundaries, publish domain events internally from day one, and extract a service only when a specific pressure justifies it. Extraction from a well-modularised monolith is measured in weeks. Recombining prematurely split services is measured in quarters.

Domain-driven design

Finding service boundaries that hold

How boundaries are decided

Boundaries come from the domain. We run event storming with your domain experts to map business events chronologically, cluster them into aggregates, and identify where the same word changes meaning. A boundary is right when a service can be correct using only its own data and a local transaction.

  1. Event storming with domain experts

    A facilitated session where operations, finance, support and engineering place business events on a timeline. Domain experts, not just architects, because the vocabulary lives with the people doing the work.

    Two to four half-day sessions

  2. Identify aggregates and invariants

    Group events around the entities whose consistency must be enforced together. An aggregate is the unit that must be transactionally consistent. Aggregates never straddle service boundaries.

    Invariants written down explicitly

  3. Map bounded contexts by language

    Where the same noun means different things, you have found a context boundary. A "customer" in billing carries payment terms and credit status. A "customer" in support carries entitlement and contact history. Forcing one shared model is how you get a table with ninety columns.

    Ubiquitous language per context

  4. Define context relationships

    For each pair of contexts, name the relationship: shared kernel, customer and supplier, conformist, or anticorruption layer. This determines who absorbs the cost when one side changes, and it is a negotiation between teams as much as a technical choice.

    Documented as a context map

  5. Test each candidate boundary

    Three questions. Can it be correct with a local transaction? Can one team own it end to end? Can it be deployed without coordinating with another team? Three yeses is a viable service. Any no and the boundary moves.

    The distributed-transaction smell test

  6. Sequence extraction by value and risk

    Extract the context with the highest change frequency and the lowest data entanglement first. It delivers the earliest benefit and teaches the team the pattern on the easiest case rather than the hardest.

    One context at a time, never a big bang

Communication

Synchronous or asynchronous, and which event pattern

Sync or async

Use synchronous calls when the caller cannot proceed without the answer and the user is waiting. Use asynchronous events when the caller only needs the work to happen eventually. Every synchronous call is a runtime dependency that couples availability: three services at 99.9 percent chained synchronously give you 99.7 percent.

Synchronous versus asynchronous communication
FactorSynchronous (HTTP, gRPC)Asynchronous (events, queues)
CouplingTemporal. Both services must be up at the same momentOnly the broker must be up. Consumers can be down and catch up
Availability arithmeticMultiplies. Chained dependencies compound failureIsolates. A slow consumer creates a backlog, not an outage
ConsistencyImmediate. The caller sees the resultEventual. The interface must be designed for it, with honest pending states
Error handlingImmediate and returnable to the callerRetries, dead-letter queues and an operational process to drain them
DebuggingStraightforward request and response with a traceRequires correlation identifiers and message tracing to reconstruct a flow
User experience fitReads, validation, authorisation, anything where the user waits for an answerNotifications, reporting, downstream side effects, cross-system propagation
Use whenThe caller genuinely cannot continue without the responseThe caller needs the work done, not the answer now

The most common architectural mistake we correct is a synchronous chain three or four services deep on a user-facing path. Each hop adds latency and a failure mode, and the timeout budget usually has not been calculated. If the total budget is 2 seconds and there are four hops, no single hop can be allowed 2 seconds, yet that is frequently the configured default.

Event-driven patterns and their trade-offs
PatternWhat the event containsBenefitCostUse when
Event notificationMinimal. An identifier and event type, such as OrderPlaced with an order idLoose coupling, tiny payloads, no data duplicationConsumers must call back to fetch detail, which reintroduces a synchronous dependency and load on the producerFew consumers, cheap lookups, and the producer can absorb the callback traffic
Event-carried state transferThe full relevant state at the time of the eventConsumers are autonomous. They can process with no callback, even while the producer is downData duplication, larger payloads, and every schema change ripples to consumers. Stale local copies must be reasoned aboutMany consumers, expensive or rate-limited lookups, or availability isolation is the point
Event sourcingThe event log is the system of record. State is derived by replaying eventsComplete audit history, temporal queries, the ability to rebuild any projection and to answer questions you had not thought of yetSubstantial. Schema evolution of historical events, snapshotting, eventual consistency everywhere, and a mental model most teams have never usedGenuine audit and temporal requirements: ledgers, trading, clinical records, regulated workflow. Rarely justified elsewhere
CQRSSeparate write model and one or more read models kept in sync by eventsReads and writes scale and are modelled independently. Complex queries stop distorting the write modelTwo models to maintain, replication lag visible to users, and read-your-own-writes needs deliberate handlingRead and write loads differ by an order of magnitude, or reporting queries are damaging transactional performance
Transactional outboxEvents written to an outbox table in the same transaction as the state change, then published by a relayRemoves the dual-write problem without distributed transactionsA relay process to run and monitor, plus outbox table growth and cleanupAlways, whenever a service both writes state and publishes events. This is a default, not an option
Saga (orchestrated or choreographed)A sequence of local transactions with compensating actions on failureBusiness processes spanning services without two-phase commitCompensation logic is business logic and must be designed with the domain. Partial states are visible. Testing is genuinely hardLong-running multi-service processes such as order fulfilment. Not a substitute for a correct boundary

Event sourcing and CQRS are frequently adopted together and frequently regretted. They are powerful in the narrow set of domains that genuinely need history as a first-class concern. If your requirement is "we want an audit trail", an append-only audit log alongside a conventional model gives you that at a fraction of the cost.

Messaging

Choosing a broker, and getting delivery semantics right

Messaging technology selection

Choose the simplest broker that meets the requirement. SQS or EventBridge for most work on AWS. Kafka or MSK when you need log replay, ordered partitioned streams, or multiple independent consumer groups over retained history. Kafka is a platform commitment, not a queue.

Messaging technology comparison
TechnologyModelOrdering and retentionOperating costWhen it wins
Apache Kafka (self-managed)Partitioned, replayable commit log with independent consumer groupsOrdered per partition. Retention by time or size, replay from any offsetHigh. Brokers, storage, rebalancing, partition planning, upgrades. Realistically a platform teamHigh-throughput streams, event sourcing, many independent consumers over retained history, stream processing
Amazon MSK or Confluent CloudManaged KafkaSame as KafkaModerate. Cluster and partition design still yours; patching and replication are notYou need Kafka semantics without owning broker operations. Our default when Kafka is genuinely required
Amazon SQS and SNSPoint-to-point queues with visibility timeouts, plus FIFO queues. SNS adds topic fan-out, usually to several SQS queues so each consumer gets its own bufferStandard queues are unordered and at-least-once. FIFO gives ordering per message group at lower throughput. SNS offers no ordering and no retention beyond delivery retriesLowest. No infrastructure at allWork queues, background jobs, simple one-to-many fan-out, absorbing bursts. Where we start unless something rules it out
Amazon EventBridgeEvent bus with content-based routing rules, schema registry and archive with replayNo ordering. Archive supports bounded replayLow. Serverless with per-event pricingCross-domain and cross-account event routing, third-party SaaS events, routing rules you want to change without redeploying producers
RabbitMQ or Amazon MQBroker with rich routing: exchanges, bindings, priorities, delayed messagesOrdered per queue. Messages removed on acknowledgement, no replayModerate. Cluster and queue management, mirrored queue configurationComplex routing topologies, priority queues, per-message TTL, or an existing AMQP or JMS estate to preserve
Amazon Kinesis Data StreamsSharded stream with a retention windowOrdered per shard, retention up to 365 daysLow to moderate. Shard capacity planning is the main workAWS-native streaming with Lambda or Firehose consumers, telemetry and clickstream ingestion, when Kafka would be over-provisioned
Database table as a queuePolling with row lockingWhatever you implementVery low initially, and it becomes your problem as volume growsGenuinely low volume with existing transactional guarantees. Honest and often correct for a small system. It stops being correct somewhere around a few hundred messages per second

We choose Kafka far less often than clients expect. The question that settles it: do you need to replay history to a new consumer, or have multiple consumer groups read the same stream at different positions? If not, SQS and EventBridge will do the job with a fraction of the operating cost.

Delivery semantics, honestly

  • At-most-once. Fire and forget. Messages can be lost. Acceptable only for genuinely disposable telemetry.
  • At-least-once. What every practical broker gives you. Duplicates will happen, caused by retries after ambiguous timeouts, consumer restarts before acknowledgement, and rebalances. Design for it.
  • Exactly-once. Achievable only within a single system's boundary. Kafka transactions provide it for Kafka-to-Kafka processing. The instant you write to a database, call an API or send an email, the guarantee ends.
  • Exactly-once effects. The achievable and commercially meaningful goal: at-least-once delivery plus idempotent consumers. Processing the same message twice produces the same state as processing it once.

Making consumers idempotent

  • Idempotency keys. Every mutating request and message carries a stable client-generated key. The consumer records processed keys and short-circuits repeats. Retention on that record must exceed the maximum possible retry window.
  • Natural idempotency. Prefer operations that are safe to repeat. Setting a status to shipped is idempotent. Incrementing a counter is not. Reshape the operation where you can.
  • Conditional writes. Optimistic concurrency on a version number, or a unique constraint that makes a duplicate insert fail loudly rather than silently double-count.
  • The outbox pattern. Write state and event in one local transaction, publish from the outbox with a relay or change-data-capture. Without it you get published events for rolled-back changes, or committed changes with no event.
  • Dead-letter queues with an owner. Every DLQ needs an alert, a runbook and a named owner. An unmonitored DLQ is a silent data-loss mechanism, and we find them in most estates we review.
  • Poison-message handling. A bounded retry count, then quarantine. Without it one malformed message blocks a partition indefinitely.

API design

REST, GraphQL, gRPC, versioning and the gateway

API style selection

Use REST at the edge for public and partner APIs, because HTTP caching, tooling and comprehensibility win where you do not control the consumer. Use GraphQL when many clients need different projections of the same graph. Use gRPC between internal services where latency, streaming and a strict schema matter.

REST, GraphQL and gRPC decision table
FactorREST over HTTPGraphQLgRPC
Best forPublic and partner APIs, webhooks, anything a third party integrates without your helpRich clients, aggregation across services, mobile clients minimising round tripsInternal service-to-service, high call volume, bidirectional streaming
CachingExcellent. CDN, proxy and browser caching work as designedHard. POST by default, so you need persisted queries or an application-level cacheNone at the HTTP layer. Application-level only
Over-fetchingPresent unless you add sparse fieldsets or purpose-built endpointsSolved. Clients request exactly what they needControlled by message definition, so a new shape means a new method or field mask
Schema and typingOpenAPI, adopted with varying disciplineStrong, introspectable schema by defaultStrongest. Protobuf with generated clients in every supported language
Browser supportNativeNativeRequires gRPC-Web and a proxy
Main riskEndpoint proliferation and inconsistent conventions across teamsExpensive nested queries, the N+1 resolver problem, and authorisation that must be enforced per fieldTight coupling to generated stubs, and a harder debugging story without good tooling

REST at the edge and gRPC internally is a sound default. Adding GraphQL is justified when you have several distinct client types with genuinely different data needs. Adding GraphQL because a single web client makes too many requests is usually solved more cheaply with a purpose-built aggregation endpoint.

Versioning and deprecation policy

  • Additive changes are not versioned. New optional fields and new endpoints ship without a version bump. Consumers must tolerate unknown fields, and we state that in the contract.
  • Breaking changes get a new major version. Path-based for REST, a new package for Protobuf. URI versioning over header negotiation because it is visible in logs, caches and support conversations.
  • Two major versions supported concurrently, with a minimum twelve-month overlap for public APIs and ninety days for internal ones.
  • Deprecation is announced with data. Sunset and Deprecation headers on responses, a changelog entry, and direct contact with the consumers your telemetry shows are still calling the old version.
  • Never change semantics under a stable contract. Altering what a field means without renaming it is the most damaging kind of breaking change, because every test still passes.

What belongs in the API gateway

  • Authentication. Token validation, JWT signature and claim checks, mTLS termination for partner traffic. Verify once at the edge, propagate a verified identity inward.
  • Rate limiting and quotas. Per consumer and per plan, with clear 429 responses carrying Retry-After.
  • Routing and edge composition. Path and header routing to services, canary weighting, and blue-green switching.
  • Observability. Correlation identifier generation, structured access logs, and per-route latency and error metrics.
  • What does not belong. Business logic, data transformation beyond trivial shaping, and authorisation decisions that depend on domain state. A gateway that grows business logic becomes a shared bottleneck every team must queue behind, and it will not be tested like application code.

Legacy integration: adapters, and what to avoid

The pattern is always the same. An adapter service speaks the legacy protocol on one side and a clean, owned contract on the other, with an anticorruption layer so legacy field names, sentinel values and quirks never leak into new code. What varies is the transport.

Legacy integration approaches
ApproachWhere you find itHow we handle itRisk
SOAP and WSDLInsurance, banking, telecoms, ERP and government systemsGenerate a client from the WSDL, wrap it in an adapter exposing REST or gRPC, translate faults into typed errors, and cache aggressively where the data allowsManageable. Watch for WS-Security configuration, envelope size limits, and sessions with server-side affinity
Flat or fixed-width files over SFTPPayroll, banking settlement, EDI, logistics, retail supplier feedsIdempotent ingestion keyed on file name and content hash, strict schema validation, a quarantine directory for rejects, and reconciliation counts published as metricsModerate. The real risks are silent partial files, duplicate delivery and a missing file nobody notices for a day. Alert on absence, not only on failure
IBM MQ, JMS and message-oriented middlewareEstablished enterprise estatesA bridge consumer translating onto your modern broker, preserving correlation identifiers and applying idempotency at the boundaryModerate. Transaction semantics differ from cloud brokers and must be mapped explicitly rather than assumed
Direct database-level integrationVery common, and almost always a mistakeWe treat it as debt to be retired. If unavoidable, read-only against a replica, through views owned by the source team, with a written agreement on schema change noticeHighest. It couples you to another system internal schema, so their refactor becomes your outage. There is no contract, no validation and no invariant enforcement. Writing into another system tables bypasses its business rules entirely
Screen scraping and robotic automationWhen a system has no interface at all and the vendor will not build oneOnly as an explicitly temporary bridge, with a documented exit plan, a date, and monitoring that alerts the moment the interface changesHighest and most brittle. A cosmetic UI change breaks it, and credentials must be stored to drive it. We will build it if it is genuinely the only route, and we will say plainly that it is a stopgap

Database-level integration deserves the emphasis. It is the fastest thing to build and the most expensive thing to own. The source team did not agree to a contract, so they are entitled to change the schema whenever they like, and your integration breaks without anyone having done anything wrong. If you inherit one, put retiring it on the roadmap.

Honest answer

The failure modes distribution introduces, and how we contain them

What distribution costs you

A distributed system fails partially, which is harder than failing completely. Timeouts without budgets cause cascades, retries without jitter cause synchronised storms, and a shared thread pool lets one slow dependency exhaust capacity for everything. These are not edge cases, they are the normal operating condition at scale.

  • Timeout budgets calculated end to end. If the user-facing budget is 2 seconds across four hops, each hop gets a fraction and passes its remaining budget downstream. A downstream service that knows only 40 ms remain should fail immediately rather than start work
  • Circuit breakers on every remote dependency. Open on an error-rate threshold, half-open probe after a cooldown, and a defined fallback: cached data, a degraded response, or an honest error. Failing fast beats queueing behind something already broken
  • Bulkheads. Separate connection pools and thread pools per dependency, so one slow downstream cannot consume all capacity. This is the single most effective containment pattern and the most frequently missing
  • Retry with exponential backoff and full jitter, only on idempotent operations, with a hard attempt cap. Retries without jitter synchronise into a thundering herd that keeps a recovering service down
  • Load shedding and back pressure. Reject beyond known capacity with 429 or 503 rather than accepting work you cannot complete. A queue that grows without bound converts a slowdown into an outage
  • Graceful degradation designed per feature. Decide in advance which features may be switched off to protect the revenue path, and put them behind flags so the decision can be executed in seconds
  • Distributed tracing on every request, with a correlation identifier propagated across HTTP, gRPC and message headers. Without it, cross-service root cause analysis is guesswork
  • Consumer-driven contract tests with Pact. Consumers publish expectations, providers verify them in their own pipeline, and a provider cannot deploy a change that breaks a known consumer. This replaces the fragile full-estate integration environment
  • Chaos exercises in non-production. Inject latency, kill instances, sever a dependency, exhaust a connection pool. Every resilience pattern above is a hypothesis until it has been tested
  • Per-service SLOs with burn-rate alerting, so you page on user-visible impact rather than on every individual threshold breach

Answers

API and microservices questions

Should we move to microservices?

Most likely not yet. Microservices solve an organisational scaling problem: too many engineers contending over one deployment unit. If you have fewer than about thirty engineers, or you cannot name the deployment contention that is slowing you down, a modular monolith with enforced internal boundaries gives you almost all of the design benefit and none of the distributed-systems cost. We have talked more clients out of microservices than into them.

What is a modular monolith, and why do you recommend it first?

One deployable artefact containing modules with explicit public interfaces, separate database schemas per module, no cross-module table access, and communication through in-process interfaces or a local event bus. You get domain boundaries, independent testability and clear ownership while keeping a single transaction, a single deployment and a stack trace that spans the whole request. When a module genuinely needs independent scaling or deployment, it extracts cleanly because the boundary already exists.

How do you decide where service boundaries go?

From the domain, not the org chart or the database. We run event storming with your domain experts to map business events, identify aggregates and consistency requirements, then place boundaries where the language changes meaning. If two candidate services need a distributed transaction to stay correct, the boundary is in the wrong place and they should be one service.

Is exactly-once delivery possible?

Not across a network in the general case. What is achievable is at-least-once delivery combined with idempotent consumers, which produces exactly-once effects. That is what matters commercially. Kafka transactions give exactly-once semantics within Kafka, and that guarantee ends the moment you write to an external system. Any vendor claiming end-to-end exactly-once is describing at-least-once plus deduplication.

What is the outbox pattern and why does it matter?

You cannot atomically write to your database and publish to a broker. The outbox pattern writes the event into an outbox table inside the same database transaction as the state change, and a separate process reads that table and publishes. It converts a distributed-transaction problem into a local one. Without it you eventually get state changes with no event published, or events published for changes that rolled back.

REST, GraphQL or gRPC?

REST for public and partner APIs where cacheability, tooling and comprehensibility matter most. GraphQL when many different clients need different shapes of the same data and over-fetching is a genuine cost, accepting the caching and query-cost complexity that follows. gRPC for internal service-to-service calls where latency, streaming and a strict schema matter. Most estates end up with REST at the edge and gRPC inside, which is a reasonable place to land.

Can you integrate with our mainframe or legacy SOAP systems?

Yes. The pattern is an adapter service that speaks the legacy protocol on one side and a clean modern contract on the other, so the legacy shape does not leak into new code. We work with SOAP and WSDL, fixed-width and flat files over SFTP, IBM MQ, ODBC and stored-procedure interfaces, and where nothing else exists, robotic or screen-level automation as an explicitly temporary measure with a documented exit.

What does microservices actually cost you that a monolith does not?

Network calls that used to be function calls and can now fail partially, distributed tracing to answer questions a stack trace used to answer, eventual consistency visible in the user interface, schema and contract versioning across independently deployed services, per-service pipelines and on-call, and local development that needs many services or good fakes. Budget an extra platform and observability workstream. That cost is worth paying when the organisational problem is real, and pure waste when it is not.

Start with a three-week architecture review

Fixed fee. You get a context map from event storming with your domain experts, a decomposition recommendation that may well be "stay with a modular monolith", a messaging and delivery-semantics design, an integration inventory with the risk on each, and a sequenced plan. Yours to keep either way.