0% found this document useful (0 votes)
5 views7 pages

Introduction to RxJS and Reactive Programming

The document introduces RxJS and Reactive Programming, emphasizing the use of observable sequences for handling asynchronous events in a declarative manner. It outlines key concepts such as Observables, Operators, and Subjects, along with their benefits over traditional Event-Driven Programming. Additionally, it discusses testing challenges, debugging techniques, and best practices for building reactive applications and microservices.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views7 pages

Introduction to RxJS and Reactive Programming

The document introduces RxJS and Reactive Programming, emphasizing the use of observable sequences for handling asynchronous events in a declarative manner. It outlines key concepts such as Observables, Operators, and Subjects, along with their benefits over traditional Event-Driven Programming. Additionally, it discusses testing challenges, debugging techniques, and best practices for building reactive applications and microservices.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Introduction to RxJS

Reactive Programming (RP) extends EDP by treating sequences of events/values over time as
first-class "streams" or "collections" that can be queried, transformed, filtered, and composed
declaratively and functionally. Instead of imperative event handlers that mutate state or trigger side
effects directly, RP emphasizes purity, composability, backpressure handling, and declarative data
flow. Events become observable sequences supporting multiple emissions, completion, and
errors—similar to arrays but asynchronous and push-based.

[Link]
Event-Driven Programming

Key Benefits Over Traditional EDP:

●​ Declarative operators (map, filter, merge, debounce, throttle, flatMap/switchMap) replace


manual state management and nested callbacks.
●​ Built-in handling for concurrency, cancellation, error recovery, and multicast.
●​ Better mental model via marble diagrams (time flows left-to-right; marbles represent
values/errors/completion).
●​ Scales to complex async flows (e.g., user input throttling, real-time data merging from
multiple sources, WebSocket streams).

RxJS Core Concepts (JavaScript/TypeScript library; v7+ current as of 2026):

Observable: Represents a lazy, push-based collection of future values/events. Created via creation
functions (of, from, interval, fromEvent, new Observable(subscriber => {...})). Cold by default
(execution starts on subscribe; each subscriber gets own execution).​
JavaScript​
import { fromEvent, interval } from 'rxjs';
const clicks$ = fromEvent(document, 'click'); // DOM event stream
●​ const ticks$ = interval(1000); // emits 0,1,2,... every second

Observer: Object (or callbacks) that reacts: { next: v => ..., error: e => ..., complete: () => ... }.
subscribe(observerOrNextFn) returns Subscription.​
JavaScript​
clicks$.subscribe({
next: e => [Link]('Clicked at', [Link]),
error: err => [Link](err),
complete: () => [Link]('Stream ended')

●​ });
●​ Subscription: Execution handle; call .unsubscribe() to cancel (stops emissions, cleans
resources). Critical for avoiding memory leaks in long-lived streams (e.g., DOM events,
WebSockets).

Operators (pipe-able, pure functions; chain with .pipe(op1(), op2())): Transform/filter/combine


streams functionally. Common categories: Transformation (map, scan like reduce, pluck), Filtering
(filter, debounceTime, throttleTime, distinctUntilChanged), Combination (merge, concat,
combineLatest, switchMap—cancels previous inner on new outer), Error Handling (catchError, retry),
Utility (tap for side-effects).​
JavaScript​
import { fromEvent, map, scan, debounceTime, distinctUntilChanged } from 'rxjs';
fromEvent(document, 'input')
.pipe(
map(e => ([Link] as HTMLInputElement).value),
debounceTime(300), // wait for typing pause
distinctUntilChanged(), // ignore duplicates
scan((acc, val) => acc + [Link], 0) // accumulate total chars typed
)

●​ .subscribe(total => [Link]('Total typed chars:', total));​


Marble Diagram Example (map operator: input marbles transformed to output). Visuals
clarify timing and transformations.
[Link]

[Link]
Subjects: Multicasting (hot) observables + observers. Variants: Subject (no initial value),
BehaviorSubject (replays latest), ReplaySubject, AsyncSubject. Useful for event buses or bridging
imperative to reactive.​
JavaScript​
import { Subject } from 'rxjs';
const subject = new Subject<number>();
[Link](v => [Link]('A:', v));

●​ [Link](42); // Multicasts to all subscribers


Schedulers: Control execution context/concurrency (virtual time in tests). queueScheduler,
asyncScheduler, animationFrameScheduler, asapScheduler. Use observeOn/subscribeOn.​
JavaScript​
import { asyncScheduler } from 'rxjs';

●​ [Link](() => [Link]('Delayed'), 2000);

Differences from Promises: Promises are single-value, eager, uncancellable, no operators/multicast.


Observables handle 0..N values, lazy, cancellable, composable.

Project Reactor (Java/Spring WebFlux counterpart): Similar concepts but typed and
backpressure-native (reactive streams spec). Mono<T> (0/1 item, like single-value
Observable/Promise), Flux<T> (0..N, like Observable). Operators nearly identical (map, flatMap,
filter, etc.). Schedulers: [Link](), single(), parallel(), boundedElastic(). Use for
backend reactive APIs/microservices. Testing uses StepVerifier (sequential expectations + virtual
time). Parallels RxJS closely; choose based on ecosystem (JS frontend/web vs. Java/Spring backend).

Marble Diagrams: Standard visualization tool. Time horizontal; symbols: - (time unit),
letters/numbers (values), (complete), # (error), ( ) grouping, ^ subscription point, spaces ignored.
Essential for reasoning about operators and debugging.

Part 2: Testing and Debugging Event-Driven Applications


2.1 Challenges in Testing Asynchronous and Event-Based Code

Event-driven/async code introduces non-determinism absent in synchronous unit tests:

●​ Timing & Race Conditions: Events arrive in unpredictable order/timing (network, user
input, timers). Tests may flake (pass/fail intermittently).
●​ Non-Deterministic Execution: Multiple subscribers, hot/cold observables, concurrent
operators (merge, combineLatest) produce varying orders.
●​ Error Propagation & Handling: Errors in one branch can terminate streams or require
recovery; hard to assert in isolation.
●​ Side Effects & External Dependencies: DOM events, HTTP, databases, message
brokers—difficult to isolate without heavy mocking.
●​ Control Flow Complexity: Long operator chains or nested flatMap/switchMap obscure
paths; callback/promise hell evolves into "observable hell."
●​ Resource Leaks: Unsubscribed streams in tests cause hangs/memory issues.
●​ Distributed Events: In microservices/EDA, events cross service boundaries; integration tests
need brokers, schemas (Avro/Protobuf), and correlation. Best practices: Prefer unit tests with
virtualized time; integration/contract tests for event schemas; Test Pyramid (many unit → few
E2E).

2.2 Simulating Events for Unit Testing

Goal: Make async deterministic & fast.

RxJS Subjects: Manual control—[Link](value), .error(), .complete(). Perfect for simulating


emitters.​
JavaScript​
const input$ = new Subject<string>();
// Test code that subscribes to input$

●​ input$$ .next('test'); input $$.complete();

Marble Testing with TestScheduler (virtual time; synchronous & deterministic execution). Use run()
block; cold() (cold obs), hot(); expectObservable(obs$.pipe(...)).toBe('expected-marble', valuesMap).
Flushes virtual time automatically. Solves timing issues perfectly.​
JavaScript​
import { TestScheduler } from 'rxjs/testing';
import { map } from 'rxjs';

const testScheduler = new TestScheduler((actual, expected) => {


expect(actual).toEqual(expected); // or deep equal
});

[Link](({ cold, expectObservable }) => {


const source$ = cold('-a-b-c-|', { a: 1, b: 2, c: 3 });
const result$ = source$.pipe(map(x => x * 10));
expectObservable(result$).toBe('-a-b-c-|', { a: 10, b: 20, c: 30 });

●​ });​
Test complex operators (debounce, merge, switchMap) with precise timing.
●​ Fake Timers: Jest/Vitest [Link]() + [Link](ms) to control
interval, timer, debounceTime.
●​ Mocking Sources: [Link]('rxjs', () => ({ fromEvent: [Link]() })); then provide controlled
observable. For DOM: jsdom + manual dispatchEvent. For Node EventEmitter: mock
instance, call .emit(event, data).
●​ Reactor (Java): [Link](flux).expectNext(1,
2).thenAwait([Link](1)).expectNext(3).verifyComplete(); or
virtualTimeScheduler. Similar marble-like expectations.

2.3 Using Debugging Tools and Probes for Event Flows

Traditional debuggers struggle with async push-based flows (breakpoints miss emissions).

tap() / do() Operator (side-effect probe without altering stream): Log values, errors, subscriptions.
Non-destructive; place between operators.​
JavaScript​
source$.pipe(
tap({ next: v => [Link]('Emitted:', v), error: e => [Link](e) }),
map(...)

●​ )
●​ Manual Marble Diagrams & Dependency Graphs: Draw on paper/whiteboard: sources →
operators → sinks. Identify hot/cold issues (multiple arrows → .share() or shareReplay()).
Reference [Link]. Build accurate mental model first.
●​ Browser/IDE Tools: Chrome DevTools (Sources, Network, Performance); set breakpoints in
operator implementations (hard). Source maps essential. VS Code + RxJS debugger
extensions. Angular DevTools shows RxJS traces/change detection if applicable.
●​ RxJS DevTools / Extensions: Browser extensions visualize active subscriptions, marble-like
live views (limited availability; check current ecosystem). Logging frameworks with Rx
integration.
●​ Custom Probes: Higher-order operators that wrap with logging/metrics; AOP-style in
Java/Reactor.

2.4 Logging and Monitoring in Distributed Event Systems

In microservices/EDA (events via Kafka, RabbitMQ, Pulsar, AWS EventBridge), single logs are
insufficient—need correlation across producers, brokers, consumers.

●​ Structured Logging + Correlation IDs: Use JSON logs; propagate Trace-ID / Span-ID /
Correlation-ID (e.g., via headers in events or MDC in logs). Tools: Winston/Pino (Node),
SLF4J/Logback (Java). Avoid logging sensitive data; use log levels + sampling.
●​ Centralized Aggregation: ELK/EFK Stack (Elasticsearch, Logstash/Fluentd/Filebeat,
Kibana) or Grafana Loki + Promtail. Search/filter by trace ID across services.
●​ Distributed Tracing: OpenTelemetry (instrumentation for events, auto-instrumentation),
Jaeger/Zipkin. Each event hop becomes a span; visualize end-to-end latency, errors,
bottlenecks. Critical for async flows where request-response tracing fails. Instrument
producers/consumers/brokers.

[Link]
Building and understanding reactive microservices using Eclipse ...

●​ Metrics & Alerting: Prometheus (counters for events processed, gauges for queue depth,
histograms for latency) + Grafana dashboards. Alert on high error rates, backlog, processing
time. Track per-event-type metrics.
●​ Best Practices: Immutable logs, correlation at publish/consume, schema registry for events,
dead-letter queues with monitoring, sampling in production, security (log encryption/access
control). Combine with service meshes (Istio/Linkerd) for automatic tracing.

Key Takeaways & Exercises:

●​ Prefer reactive patterns for complex EDP to reduce bugs.


●​ Always test with virtualized time/marble testing.
●​ Debug via visualization (marbles + tap) before code changes.
●​ Observability = Logs + Traces + Metrics with correlation is non-negotiable in distributed
EDA.
●​ Exercise ideas: Implement debounce search with RxJS + marble tests; trace a multi-service
event flow in a toy Kafka setup with Jaeger; draw marble diagrams for switchMap vs flatMap.

These notes provide a solid foundation; refer to official docs ([Link], [Link]) for latest
APIs and practice extensively with code.

You might also like