Zum Inhalt springen

The design pattern making Dependency Injection Obsolete

When using traditional dependency injection (DI) systems, like in Angular, components are tightly coupled to their (injected) dependencies, requiring explicit declarations and a lot of intricate configuration.

The Limits of Dependency Injection

Dependency injection, while effective for managing dependencies, introduces complexity. In Angular, components must declare dependencies in constructors or modules, creating rigid relationships. Scaling applications often leads to sprawling dependency graphs, making maintenance and refactoring cumbersome. DI also struggles with dynamic or cross-module communication, requiring additional services or state management solutions.
Testing still requires mocks and stubs of every injected dependency, which keeps the overall QA effort considerable to maintain.

The Message Bus Pattern: A Paradigm Shift

The message bus pattern decouples components by routing messages through a centralized pub/sub hub. Components publish events or commands to the bus and subscribe to relevant messages, unaware of each other’s existence.

This loose coupling simplifies scalability, enhances modularity, and reduces refactoring overhead. Unlike DI, which binds components to specific implementations, the message bus enables dynamic, event-driven communication, making it ideal for modern, reactive applications.

Why Message Bus Outshines DI

Decoupling: Components interact via messages, not direct references, eliminating tight dependency bindings.
Flexibility: New components can subscribe or publish without altering existing code.
Scalability: The bus handles growing complexity without exponential configuration overhead.
Modularisation: features are siloed in their respective components, reducing the likelyhood of code conflicts between developers.
Testability: Components can be tested declaratively, in isolation or in group by simulating bus messages.

In practice, a message bus replaces DI’s injector with a publish-subscribe system. For example, instead of injecting a UserService into a component, the component subscribes to a UserUpdated event on the bus. This approach aligns with reactive programming principles, enabling seamless integration with frameworks like RxJS.

TOPS: The Observable Plugin System

To harness the message bus pattern’s potential, we’re introducing a new library: the-observable-plugin-system, TOPS for short, is the first streams-oriented message bus for JavaScript. TOPS leverages RxJS observables and adds request/response capabilities to create a flexible, loosely-coupled architecture for modern applications.

TOPS Implementation Example

// modules/home-page.ts
import type { Protocol } from "..";

import { match } from "../operators/match";
import { map } from "rxjs";
import { rml } from "rimmel";

const template = () => rml`
    <h1>Hello, World</h1>
    <p>Welcome to the home page!</p>
`;

export default ({ ROUTE, RENDER }: Protocol, config) => {

    ROUTE.pipe(
        match('/'),
        map(template),
    ).subscribe(RENDER);

};

The code above is a simple „home page“ module. It listens to the ROUTE topic, an observable stream coming from the router.
On every route event, we filter match('/') the current path and if it matches, we pass it through a template, finally emitting the resulting HTML to the RENDER topic.
All topics are Observable Subjects, readable and writable. A „display“ module listens to RENDER messages and renders the HTML on the page.

Since a module like the home page above doesn’t have side effects, testing it is trivial using streams-oriented testing libraries like LeapingBunny. We emit events in and check events out.

TOPS enables components to communicate via observable streams, supporting reactive, asynchronous workflows. Plugins can extend functionality by subscribing to or publishing events, fostering a modular ecosystem. Unlike Angular’s DI, TOPS requires no predefined dependency declarations, making it ideal for dynamic, event-driven applications.

A Reference architecture

Enough words, let’s see this in practice.
The following working example illustrates everything with an example: a simple demo webapp in streams-oriented style, where everything is a module/plugin, and everything is a stream.

View on Stackblitz

Conclusion

The message bus pattern, exemplified by TOPS, eliminates the rigidity of traditional dependency injection. By enabling loose coupling through event streams, it simplifies application design, enhances scalability, and aligns with the streams-oriented programming paradigm. TOPS empowers developers to build modular, maintainable systems, rendering traditional DI systems like Angular’s unnecessary.

Learn More

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert