Why “Microservice” Is a Bad Architectural Philosophy

Case study: Splitting Movie Reconciliation from the Movie Database Is a Poor Service Boundary

Executive Summary

The current design separates the logic that updates Movie Database (Movie Database) records into a standalone Movie Updates Service, while leaving ownership of the underlying data and datastore inside Movie Database.

This creates an artificial distributed boundary around behaviour that is fundamentally part of Movie Database’s responsibility.

The Movie Updates Service cannot directly access the data it needs, so Movie Database must expose additional APIs purely to allow the Movie Updates Service to reconstruct enough of Movie Database’s internal state to perform merge, visibility, deletion, and update decisions. The Movie Updates Service then sends commands back to Movie Database to apply those decisions.

The result is a distributed workflow that replaces what would otherwise be local domain logic:

Simple model:

External updates
      |
      v
Movie Database
  - read current state
  - merge updates
  - apply visibility/deletion rules
  - persist changes

with:

Current split:

                    +------------------+
                    | Movie Database  |
                    |------------------|
                    | Owns datastore   |
                    | Owns records     |
                    +--------+---------+
                             ^
                             | read APIs
                             |
External sources ---> Movie Updates Service
                             |
                             | mutation APIs / commands
                             v
                    +------------------+
                    | Movie Database  |
                    +------------------+

The system has therefore separated behaviour from the data it governs, and then created a set of network APIs to reconnect them.

The central architectural concern is:

If a service must repeatedly retrieve another service’s state, make decisions about that state, and then instruct the original service how to mutate it, the behaviour is probably on the wrong side of the service boundary.


1. The Split Does Not Represent Independent Ownership

A good service boundary normally encapsulates:

  • data ownership;
  • business rules governing that data;
  • invariants;
  • lifecycle management;
  • persistence;
  • externally meaningful operations.

Movie Database already owns the managed records and their persistence.

The Movie Updates Service does not own an independent business capability. Its purpose is to decide how Movie Database-owned records should change.

That means ownership is divided:

ResponsibilityOwner
Record storageMovie Database
Record lifecyclePartly Movie Database, partly Movie Updates Service
Current record stateMovie Database
Merge behaviourMovie Updates Service
Visibility decisionsMovie Updates Service
Deletion decisionsMovie Updates Service
Applying mutationsMovie Database

This is not strong separation of concerns.

It is separation of data from behaviour.


2. The Architecture Replaces Local Calls with Distributed Calls

Without the split, update processing could operate against Movie Database’s domain model and repositories directly.

For example:

loadCurrentState()
fetchExternalUpdates()
merge()
applyLifecycleRules()
persist()

With the split, the equivalent workflow becomes:

call Movie Database current-state API
call additional Movie Database lookup APIs
call external source APIs
reconstruct Movie Database state remotely
perform merge
determine visibility/deletion changes
call Movie Database mutation APIs
handle failures/retries
reconcile partial completion

The essential business operation has not become simpler or more independent.

Only the transport has changed.

A useful way to characterise this is:

We took an in-process domain operation and turned it into a distributed protocol.

Every network boundary introduces additional concerns:

  • authentication and authorisation;
  • API contracts;
  • version compatibility;
  • retries;
  • timeouts;
  • partial failure;
  • observability;
  • tracing;
  • network latency;
  • deployment coordination;
  • error mapping;
  • idempotency.

Those costs are justified when the boundary provides meaningful independence.

Here, the boundary exists inside one tightly coupled data lifecycle.


3. Movie Database Must Expose APIs That Exist Only Because of the Split

A particularly strong architectural smell is the creation of APIs whose primary purpose is to allow one internal service to emulate access to another service’s datastore.

The Movie Updates Service needs enough information to:

  • identify existing records;
  • understand current state;
  • compare incoming and existing records;
  • determine which records disappeared;
  • determine which records should become invisible;
  • determine which records should be deleted;
  • calculate merged state.

Movie Database must therefore expose increasingly detailed APIs to support these decisions.

Those APIs are not necessarily meaningful domain operations.

They are often remote substitutes for:

repository.find(...)
repository.load(...)
entity.getState(...)

This creates an API surface that is driven by implementation mechanics rather than business capability.

The more sophisticated the merge becomes, the more Movie Database internals must leak through those APIs.


4. The Split Creates Tight Coupling While Pretending to Create Independence

The Movie Updates Service must understand Movie Database’s data model sufficiently well to calculate valid changes.

Therefore, changes to any of the following can affect both services:

  • record structure;
  • identity rules;
  • lifecycle semantics;
  • visibility semantics;
  • deletion rules;
  • merge precedence;
  • source priority;
  • versioning rules;
  • deduplication logic.

The two services may be independently deployable in infrastructure terms, but they are not necessarily independently evolvable.

That distinction matters.

Independent deployment is valuable when two components can actually change independently.

If a Movie Database domain change regularly requires a corresponding Movie Updates Service change, the service boundary has created deployment complexity without creating genuine autonomy.

We have achieved separation of deployment, but not separation of responsibility.


5. It Introduces a Read-Decide-Write Consistency Window

The Movie Updates Service operates on a remote snapshot of Movie Database state.

Conceptually:

T1: Movie Updates Service reads state from Movie Database
T2: Movie Updates Service calculates merge
T3: Movie Updates Service sends mutation to Movie Database

Between T1 and T3, Movie Database state may change.

This creates races such as:

Update A reads version 10
Update B modifies record to version 11
Update A calculates result based on version 10
Update A sends its mutation

The system now needs additional mechanisms such as:

  • version fields;
  • optimistic locking;
  • compare-and-set semantics;
  • idempotency tokens;
  • conflict responses;
  • retry logic;
  • merge retries.

Inside Movie Database, the scope between reading the state and applying the resulting mutation can often be much smaller and can participate naturally in the datastore’s consistency mechanisms.

The distributed split therefore manufactures consistency problems that need additional architecture to solve.


6. Partial Failure Becomes a Business Problem

A local update operation can usually fail as a unit.

A distributed update process can fail at many intermediate points:

1. External source fetch succeeds
2. Movie Database state read succeeds
3. Merge succeeds
4. First mutation succeeds
5. Second mutation times out
6. Visibility update is not applied

Now the system must determine:

  • whether the timeout occurred before or after the mutation;
  • whether the operation can safely be retried;
  • whether some records have already changed;
  • whether the Movie Updates Service must reread Movie Database;
  • whether compensation is required;
  • whether partially applied changes are acceptable.

The business operation has acquired distributed transaction characteristics despite fundamentally operating on one service’s owned state.


7. The Data Crosses the Network for No Domain Reason

The design typically causes Movie Database data to travel:

Movie Database database
   |
   v
Movie Database
   |
   | HTTP/API
   v
Movie Updates Service
   |
   | processing
   v
Movie Database
   |
   v
Movie Database database

The records leave the service that owns them so another service can decide what the owning service should do with them.

This adds:

  • serialisation/deserialisation;
  • larger network payloads;
  • latency;
  • memory pressure;
  • API pagination concerns;
  • schema duplication;
  • data exposure;
  • additional logging/security considerations.

For large record sets this can become particularly expensive.

The architecture may eventually require bulk APIs, streaming APIs, pagination, caching, and delta APIs simply to compensate for moving the processing away from the data.


8. The Movie Updates Service Risks Becoming a Shadow Movie Database Service

As update logic grows, the Movie Updates Service is likely to accumulate knowledge about:

  • Movie Database identities;
  • Movie Database schemas;
  • Movie Database state transitions;
  • Movie Database lifecycle rules;
  • source precedence;
  • deletion behaviour;
  • visibility;
  • historical state.

At that point it is effectively becoming a second implementation of the Movie Database domain model.

This produces two places where the meaning of Movie Database data lives:

Movie Database
    +
Movie Updates Service

That is dangerous because domain rules can diverge.

A future developer may reasonably ask:

Which service actually defines what a Movie Database record means?

There should be an unambiguous answer.


9. Audit Does Not Require This Split

Audit requirements are sometimes used to justify extracting processing.

However, audit is orthogonal to where the business logic executes.

Movie Database can record:

  • update received;
  • source;
  • previous state;
  • resulting state;
  • record hidden;
  • record deleted;
  • merge decision;
  • timestamp;
  • correlation ID.

It can do this through:

  • audit tables;
  • domain events;
  • an event stream;
  • structured logs;
  • a downstream audit consumer.

The existence of an audit requirement does not require the update behaviour itself to live in another service.


10. Scaling Is Unlikely to Justify the Boundary by Itself

Another possible argument is that update processing may need independent scaling.

That does not necessarily require extracting ownership of the merge logic.

Movie Database can internally use:

  • worker pools;
  • queues;
  • scheduled jobs;
  • asynchronous processors;
  • separate application processes using the same domain module;
  • horizontally scaled Movie Database instances.

The important distinction is between execution topology and domain ownership.

A workload can run asynchronously or on separate compute without turning its domain logic into a separate service boundary.

For example:

             +----------------------+
             | Movie Database      |
             |----------------------|
             | Domain model         |
             | Merge rules          |
             | Lifecycle rules      |
             | Repository           |
             +----------+-----------+
                        ^
                        |
              Movie Database update workers

The workers can scale independently while still using Movie Database-owned rules and data access.


11. A Better Boundary

A more coherent design keeps reconciliation behaviour with the owner of the data.

External Source
      |
      v
Source Adapter / Connector
      |
      | incoming update
      v
+-----------------------------+
| Movie Database             |
|-----------------------------|
| Current state               |
| Merge rules                 |
| Visibility rules            |
| Deletion rules              |
| Lifecycle rules             |
| Persistence                 |
+-----------------------------+

A source-specific component may still be useful.

Its responsibility could be:

  • authenticating with the external provider;
  • retrieving source records;
  • handling source-specific protocols;
  • converting source formats into a canonical update representation;
  • publishing the resulting update to Movie Database.

The boundary then becomes:

The adapter knows how to obtain the data.
Movie Database knows what that data means to Movie Database state.

That is a much cleaner separation of responsibility.


12. A Message-Based Design Can Preserve Decoupling

If asynchronous processing is required, the external/source layer can publish updates:

External source
      |
      v
Source Adapter
      |
      | UpdateReceived
      v
Queue / Event Bus
      |
      v
Movie Database
      |
      +-- load current state
      +-- merge
      +-- apply lifecycle rules
      +-- persist
      +-- emit audit/domain events

This still provides:

  • asynchronous processing;
  • back-pressure;
  • retry capability;
  • independent source integration;
  • operational decoupling.

But the domain decision remains with the owner of the domain state.


Proposed Solution: Movie Database Owns Reconciliation, Update Owns Acquisition

A cleaner split is to keep the Movie Updates Service, but radically reduce its responsibility.

The Movie Updates Service should not reconstruct Movie Database state, decide how records should be merged, or issue detailed mutation commands.

Instead:

Movie Database decides what needs refreshing and what an update means; The Movie Updates Service only obtains the latest facts from the movie-data provider.

The boundary becomes:

External Movie Data Provider
        |
        v
  Movie Updates Service
  - connect to external movie data provider
  - authenticate
  - fetch latest data
  - normalise source response
        |
        | latest provider state / observations
        v
 Movie Database
 - load current state
 - compare
 - merge
 - resolve conflicts
 - apply visibility rules
 - apply deletion/retention rules
 - persist
 - audit

This preserves a useful separation:

Sync owns acquisition. Movie Database owns interpretation and state.

Minimal Movie Database API

Movie Database only needs to expose a small operational API to support the update process.

For example:

GET /movies/due-for-update

or an equivalent work-allocation endpoint returns the movies, sources, or resources that Movie Database has determined require refreshing.

The Movie Updates Service then contacts the appropriate external movie data provider and returns the latest information:

POST /movies/{id}/update

Conceptually:

ask Movie Database what needs updating
        |
        v
call the relevant movie-data provider
        |
        v
receive latest external movie data provider data
        |
        v
send latest data to Movie Database
        |
        v
Movie Database decides how to apply it

The payload sent back should describe what the external movie-data provider reported, rather than instruct Movie Database how to modify its own records.

For example, this is a healthy contract:

MovieUpdate
{
    externalMovieId,
    title,
    releaseDate,
    synopsis,
    cast,
    crew,
    genres,
    observedAt
}

whereas this is evidence that too much Movie Database behaviour has leaked into Update:

HideMovie(...)
DeleteTransaction(...)
SetBalance(...)
MarkSourceExpired(...)

The Movie Updates Service should report facts. Movie Database should derive commands and state transitions.

Movie Database Owns Which Movies Need Updating

Movie Database is also the natural owner of update eligibility and scheduling.

Movie Database already knows information such as:

  • last successful metadata refresh;
  • last attempted metadata refresh;
  • movie and source status;
  • expiry;
  • availability or tombstone state;
  • retry state;
  • refresh frequency;
  • priority;
  • retention state;
  • terminal states;
  • whether an item is already being processed.

Therefore, Movie Database can expose only work that is currently eligible:

GET /updates/work?limit=100

The Movie Updates Service does not need its own scheduling database or duplicate model of Movie Database lifecycle state.

This avoids creating two independent notions of:

  • when an movie should refresh;
  • whether an movie is active;
  • whether retries are allowed;
  • whether refreshes should cease;
  • whether an movie is terminal.

The Movie Updates Service Can Become Effectively Stateless

With this model, the Movie Updates Service no longer needs to store Movie Database-owned data in order to perform reconciliation.

It may still need transient operational state for calls in flight, retries, or rate limiting, but it does not need a persistent copy of the domain model.

That removes the need for much of the infrastructure that exists only because of the current split.

The Movie Updates Service no longer needs:

  • a replica or cache of Movie Database state;
  • detailed knowledge of Movie Database’s persistence model;
  • Movie Database-specific merge logic;
  • visibility rules;
  • deletion rules;
  • retention rules;
  • record lifecycle logic;
  • mutation orchestration;
  • persistent reconciliation state.

This materially reduces the service’s operational and maintenance footprint.

Encryption Can Remain Inside Movie Database

Keeping reconciliation inside Movie Database also provides a cleaner cryptographic boundary.

The Movie Updates Service does not need access to Movie Database’s encrypted representation merely to determine what changed.

Movie Database can compare the incoming external movie data provider data against its existing state and selectively invoke cryptographic processing only where required.

For example:

incoming update
      |
      v
Movie Database compares with existing state
      |
      +-- no change ------------> no write
      |
      +-- ordinary field change -> cheap update
      |
      +-- encrypted field change -> crypto only for affected fields
      |
      +-- missing source record -> lifecycle / visibility rules
      |
      +-- deletion condition ----> retention / deletion processing

This means Movie Database can avoid decrypting or re-encrypting sensitive fields when unrelated data changes.

For example, if only a balance or timestamp changes while encrypted movie metadata remains identical, Movie Database can update only the changed fields.

This optimisation is harder when reconciliation happens outside Movie Database, because the external service either needs access to more stored state, needs cryptographic support itself, or has to make decisions with incomplete information.

Keeping crypto inside Movie Database therefore improves both efficiency and security.

Reduced Security Footprint

The revised split also narrows the Movie Updates Service’s security responsibilities.

If it does not persist Movie Database data or perform reconciliation over encrypted state, it no longer requires broad access to:

  • Movie Database data stores;
  • Movie Database encryption keys;
  • decryption services;
  • sensitive historical state;
  • internal lifecycle metadata.

Its privileged responsibility is primarily communication with external external movie data providers and delivery of the resulting observations to Movie Database.

That produces a much smaller blast radius than a service that understands both external external movie data provider connectivity and the internal representation of Movie Database-owned data.

Clearer Ownership Model

The proposed responsibilities become:

ConcernMovie DatabaseUpdate
Determine what needs updating
Scheduling and priority
Current stored state
Persistence
Merge and reconciliation
Visibility decisions
Deletion and retention
Lifecycle rules
Change detection
Encryption of stored Movie Database data
Audit of state changes
Contact the movie-data provider
provider authentication/protocol
Fetch latest source data
Handle provider-specific response formats
Normalise source response
Return external movie data provider observations to Movie Database

This is a genuine division of responsibility because each side can be described without reference to the other’s internal implementation.

Movie Database Becomes Independent of Update Source

This model also gives Movie Database useful decoupling from where updates originate.

Movie Database does not need to know whether new information came from:

  • a direct movie-data REST API;
  • external movie feeds;
  • polling;
  • a webhook;
  • a message queue;
  • a file import;
  • a manual refresh;
  • a future integration mechanism.

All of those mechanisms can produce the same logical update contract.

external movie data provider Adapter A -------external movie data provider Adapter B --------external movie feeds ---------> Sync / ingestion layer ---> Movie Database
File Import ----------/
Manual Refresh -------/

Movie Database only needs to understand the meaning of the incoming data.

This is a much stronger abstraction than moving reconciliation outside Movie Database.

Architectural Principle

The revised model follows a simple rule:

Separate the mechanism for acquiring external state from the rules for incorporating that state into owned domain data.

Or, more simply:

The Movie Updates Service should report what the external movie-data provider says; Movie Database should decide what it means.

13. When a Separate Movie Updates Service Would Make Sense

A separate service would be more defensible if it owned a genuinely independent capability.

Examples might include a service that:

  • owns its own persistent canonical dataset;
  • combines data from several domains into a new derived product;
  • makes decisions that are independent of Movie Database’s internal model;
  • has a lifecycle independent of Movie Database;
  • exposes its own externally meaningful business capability;
  • can operate without repeatedly reconstructing Movie Database internals;
  • sends Movie Database a high-level domain request rather than detailed record mutations.

For example:

Eligibility Service
    |
    | CustomerBecameIneligible
    v
Movie Database

could be a sensible boundary if eligibility is a genuine independent business domain.

By contrast:

Movie Updates Service
    |
    | set record X field A to ...
    | hide record Y
    | delete record Z
    v
Movie Database

strongly suggests that the Movie Updates Service has taken ownership of Movie Database’s internal behaviour without taking ownership of the data.


14. Warning Signs in the Current Design

The following are useful indicators that the boundary is misplaced:

  1. The Movie Updates Service cannot do meaningful work without first calling Movie Database.
  2. New Movie Database APIs exist primarily to expose data needed by the Movie Updates Service.
  3. The Movie Updates Service understands Movie Database’s internal record structure.
  4. Movie Database and Update must frequently change together.
  5. The Movie Updates Service decides how Movie Database records should be mutated.
  6. Movie Database remains responsible for validating and persisting those mutations.
  7. Significant data is transferred out of Movie Database and then effectively sent back as changes.
  8. Consistency/versioning mechanisms are required mainly because of the service boundary.
  9. Troubleshooting one update requires tracing requests across both services.
  10. Neither service can clearly claim complete ownership of the record lifecycle.

The more of these that apply, the weaker the case for the split.


15. Architectural Principle

The underlying principle is not that services are bad.

The issue is choosing a boundary that does not correspond to domain ownership.

A useful rule is:

Put behaviour with the data and invariants that behaviour governs.

Another useful test is:

If removing the network boundary would turn most of the calls between two services into ordinary method or repository calls, ask whether there are actually two independently valuable services.

Services are useful where they create genuine autonomy.

They are expensive when they simply distribute an operation that still behaves as one cohesive unit.


Right-Sized Services, Not “Micro” Services

The objective should not be to make services small.

The objective should be to give each service a coherent responsibility, clear ownership, and a boundary that earns the cost of distribution.

Some services will naturally be small. Others will be relatively large because the capability they own is itself substantial. That is not a design failure.

A useful principle is:

There is no architectural virtue in making a service micro. The objective is to make it correctly bounded.

Or, more simply:

“Micro” describes size. Architecture should describe responsibility.

The wrong question is:

How small can we make this service?

The better question is:

What is the right boundary for this responsibility?

A service should be right-sized around:

  • ownership of data and invariants;
  • cohesion of business behaviour;
  • independent evolution;
  • operational independence where it is genuinely useful;
  • security boundaries;
  • scaling characteristics;
  • failure isolation;
  • team ownership.

A small service is not automatically a good service, and a large service is not automatically a bad one.

Splitting a cohesive capability can make the architecture worse by introducing:

  • additional APIs;
  • network latency;
  • distributed consistency problems;
  • duplicated domain knowledge;
  • more deployment units;
  • more failure modes;
  • more operational infrastructure;
  • harder troubleshooting.

A network boundary is an expensive architectural boundary and should justify its existence.

Independent deployment is only valuable where independent evolution is actually useful. Creating separately deployable services that still have to change together does not provide meaningful autonomy.

This leads to a practical rule:

Use the minimum number of service boundaries needed to preserve clear ownership and genuine independence.

For Movie Database specifically, it is reasonable for the service to be substantial if movie state, reconciliation, visibility, retention, deletion, scheduling, persistence, and cryptographic handling are all part of the same cohesive lifecycle.

Extracting one of those responsibilities simply to make Movie Database smaller would weaken the design rather than improve it.

Conclusion

The Movie Updates Service split introduces substantial distributed-system complexity without establishing an independent domain boundary.

Movie Database owns the records, datastore, and ultimate mutations. The Movie Updates Service must therefore remotely reconstruct Movie Database state, apply Movie Database-specific business rules, and send the resulting decisions back to Movie Database.

This leads to:

  • unnecessary APIs;
  • data-model leakage;
  • increased coupling;
  • consistency windows;
  • partial-failure handling;
  • additional network traffic;
  • more complex testing;
  • harder troubleshooting;
  • coordinated evolution across supposedly independent services.

The architecture can be summarised as:

We separated the behaviour from the data it operates on, then built a distributed API layer to put them back together.

A better design is to keep the Movie Updates Service as a lightweight acquisition layer. Movie Database exposes which movies or sources require refreshing; The Movie Updates Service contacts the relevant external movie data provider, obtains and normalises the latest information, and sends those observations back to Movie Database.

Movie Database then retains ownership of reconciliation, merging, visibility, deletion, lifecycle rules, persistence, auditing, scheduling decisions, and cryptographic handling. This also allows Movie Database to optimise updates by avoiding unnecessary decryption or re-encryption when sensitive fields have not changed.

That preserves a meaningful boundary:

Sync owns acquisition. Movie Database owns interpretation and state.

Or, operationally:

Movie Database decides what needs refreshing and what an update means; The Movie Updates Service only obtains the latest facts from the movie-data provider.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.