Skip to main content

Command Palette

Search for a command to run...

The Web App of my CSL Datahub implementation

How a legacy PHP system remains the source of truth in a Datahub

Updated
5 min read
The Web App of my CSL Datahub implementation

Series: Designing a Microservice-Friendly Datahub
PART III — CASE STUDY: MY CSL DATAHUB IMPLEMENTATION
Previous: High-Level Architecture Overview of my CSL Datahub implementation
Next: Redis Streams as an Event Buffer in Datahub

Every Datahub needs an anchor.
In the CSL system, that anchor is not a stream, a broker, or a processor—it’s a legacy PHP web application.

This article focuses on the most important and most easily misunderstood part of the architecture: why the CSL Web App remains the source of truth, how it emits events safely, and why protecting it from infrastructure complexity is a deliberate design choice, not a limitation.

Disclaimer (Context & NDA)
The CSL Web App and Datahub integration described here were designed and implemented in 2021. While the architectural principles remain valid, some implementation details could be modernized today. To respect NDA requirements, domain-specific logic, schemas, and proprietary workflows are intentionally generalized.


The Role of the CSL Web App

The CSL Web App is built on PHP using the Yii framework (Humhub). It predates the Datahub, predates microservices, and predates event-driven architecture.

Yet it remains:

  • Business-critical

  • Actively used

  • Rich in domain logic

  • The only place where authoritative state is written

This makes it exactly the kind of system that modern architectures must learn to work with—not replace.

Its responsibilities are intentionally narrow:

  • Accept user input

  • Apply domain rules

  • Persist state to MySQL

  • Announce state changes

Everything else is someone else’s job.


MySQL: The Single Source of Truth

In the CSL architecture, MySQL is authoritative.

This means:

  • All writes happen here

  • All business invariants are enforced here

  • All other systems derive their view from here

The Web App does not attempt to:

  • Maintain denormalized read models for other services

  • Synchronize databases

  • Push data directly into other systems

A typical transactional flow looks like this:

$db->beginTransaction();

try {
    $user->email = $newEmail;
    $user->updated_at = time();
    $user->save();

    $db->commit();
} catch (Exception $e) {
    $db->rollBack();
    throw $e;
}

Only after this transaction commits does the system talk about events.


Events Describe Facts, Not Intent

One of the most important architectural decisions was that the Web App never emits commands—only events.

An event is emitted after state changes, never before.

$redis->xAdd(
    'csl:events',
    '*',
    [
        'type' => 'user.updated',
        'user_id' => $user->id,
        'occurred_at' => time()
    ]
);

This event says one thing only:

“A user was updated.”

It does not say:

  • What other systems should do

  • Which systems should react

  • Whether the update succeeded elsewhere

This keeps the Web App free from downstream assumptions.


Why Redis Streams, Not RabbitMQ

A natural question arises:
Why doesn’t the CSL Web App publish directly to RabbitMQ?

The answer is architectural protection.

Direct broker integration would mean:

  • Network dependencies inside request flows

  • Failure modes the app cannot control

  • Operational concerns leaking into core logic

  • Coupling the app to infrastructure decisions

Instead, Redis Streams act as a local, low-risk boundary.

Redis offers:

  • Fast, in-process publishing

  • Minimal configuration

  • Simple failure behavior

  • Familiar operational semantics

If Redis is temporarily unavailable:

  • The app can retry locally

  • Failures are contained

  • Users are not blocked by downstream outages

The Web App talks to Redis because Redis behaves like local infrastructure, not a distributed dependency.


Redis Streams as an Emission Layer

Inside the CSL Web App, Redis Streams are treated as an append-only log.

Key characteristics:

  • No consumer awareness

  • No routing logic

  • No retries handled here

  • No business interpretation

The Web App’s only concern is:

“Record that something happened.”

This keeps the emission logic trivial—and therefore safe.


REST APIs: Explicit, Controlled Boundaries

The CSL Web App does expose REST APIs—but only for specific purposes.

These APIs are used by:

  • The Processor service

  • Administrative tools

  • Controlled integrations

They are not used for event propagation.

Example API handler:

public function actionUpdateStatus($id)
{
    $model = User::findOne($id);
    $model->status = 'processed';
    $model->save();

    return ['success' => true];
}

REST APIs are:

  • Synchronous

  • Intent-driven

  • Explicitly permissioned

They belong to the control plane, not the data plane.


Why the Web App Does Not Orchestrate

The CSL Web App never:

  • Calls other services in response to events

  • Waits for downstream acknowledgments

  • Coordinates multi-system workflows

That work is intentionally delegated.

Why?

Because orchestration:

  • Introduces tight coupling

  • Makes failures contagious

  • Forces the app to care about others’ availability

The Web App’s job ends when it:

  1. Validates input

  2. Writes state

  3. Emits an event

Anything beyond that risks turning it into a distributed coordinator—something it was never designed to be.


Protecting the Core System

The most important outcome of this design is risk isolation.

If:

  • RabbitMQ is down

  • The Processor crashes

  • External modules misbehave

The CSL Web App:

  • Continues to function

  • Continues to accept user input

  • Continues to write correct state

This is not accidental. It is the primary success criterion.

Modern patterns are valuable only if they do not destabilize the core business system.


Legacy Systems Can Be First-Class Citizens

The CSL Web App is not “modernized” by:

  • Rewriting it

  • Forcing new paradigms into it

  • Turning it into a microservice

It is modernized by:

  • Giving it clear boundaries

  • Letting it speak in facts

  • Shielding it from infrastructure complexity

This is how legacy systems survive architectural evolution without becoming liabilities.


Mental Model Recap

The CSL Web App:

  • Owns truth

  • Emits facts

  • Accepts intent via APIs

  • Avoids downstream dependencies

Everything else reacts.

That asymmetry is intentional—and powerful.


Where We Go Next

Now that we’ve established the Web App as the source of truth, the next question is what happens after an event is emitted.

In the next article, Redis Streams as an Event Buffer, we’ll zoom in on the first hop beyond the core system—why buffering matters, how consumer groups work, and how Redis absorbs pressure so the rest of the Datahub can breathe.

Legacy systems don’t block modern architecture.
Unclear boundaries do.

Designing a Microservice-Friendly Datahub

Part 11 of 22

A series on microservice-friendly Datahub architecture, covering event-driven principles, decoupling, diving in real-world implementation with Redis, RabbitMQ, REST API, and processor service showing distributed systems communicate at scale.

Up next

High-Level Architecture Overview of my CSL Datahub implementation

Understanding the structure, roles, and data flow of my implemented CSL Datahub