Skip to content

RxJS y Reactive Streams ​

Cuando se aprende RxJS, muchos desarrolladores tienen la pregunta: "¿Cómo se posiciona RxJS dentro de la visión general de Reactive Programming?"

Esta página explica las diferencias entre RxJS y el estándar Reactive Streams, la visión general del stack tecnológico desde la capa UI hasta la capa de datos, y métodos de cooperación entre diferentes tecnologías.

Posicionamiento de RxJS ​

¿Qué es RxJS? ​

RxJS (Reactive Extensions for JavaScript) es la principal implementación de Reactive Programming en entornos de navegador y Node.js.

Características de RxJS

  • Opera en navegador y Node.js
  • Prioriza la capacidad de respuesta de la UI
  • Ligero y rápido
  • Backpressure es implícito

RxJS vs Estándar Reactive Streams ​

En Reactive Programming hay dos grandes flujos: RxJS y el estándar Reactive Streams.

¿Qué es el Estándar Reactive Streams? ​

Reactive Streams es una especificación estándar para procesamiento de flujos en JVM.

Principales implementaciones:

  • Project Reactor (Spring WebFlux)
  • RxJava 3
  • Akka Streams
  • Mutiny (Quarkus)

4 interfaces estandarizadas:

java
public interface Publisher<T> {
    void subscribe(Subscriber<? super T> s);
}

public interface Subscriber<T> {
    void onSubscribe(Subscription s);
    void onNext(T t);
    void onError(Throwable t);
    void onComplete();
}

public interface Subscription {
    void request(long n);  // Control de backpressure
    void cancel();
}

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}

Diferencia Principal: Control de Backpressure ​

AspectoRxJSEstándar Reactive Streams
PlataformaJavaScript/TypeScript (navegador, Node.js)JVM (Java, Scala, Kotlin)
BackpressureImplícito (nivel de operador)Explícito (método request(n))
PrioridadCapacidad de respuesta de UIThroughput del servidor
EstandarizaciónAPI común ReactiveXEspecificación Reactive Streams

Backpressure de RxJS (Implícito) ​

typescript
import { interval } from 'rxjs';
import { bufferTime, take } from 'rxjs';

// Backpressure controlado por operadores
interval(10)  // Emitir valor cada 10ms
  .pipe(
    bufferTime(1000),  // Bufferizar cada segundo (control implícito)
    take(5)
  )
  .subscribe(batch => console.log('Lote:', batch.length));

Backpressure de Reactive Streams (Explícito) ​

java
// Project Reactor (Java)
Flux.range(1, 1000)
    .subscribe(new BaseSubscriber<Integer>() {
        @Override
        protected void hookOnSubscribe(Subscription subscription) {
            request(10);  // Solicitar primeros 10 (explícito)
        }

        @Override
        protected void hookOnNext(Integer value) {
            System.out.println("Procesando: " + value);
            request(1);  // Solicitar siguiente 1 después de procesar (explícito)
        }
    });

IMPORTANT

Diferencia en Backpressure

  • RxJS: Control implícito con operadores (bufferTime, throttleTime, debounceTime)
  • Reactive Streams: Control explícito con método request(n)

Esta diferencia refleja las diferentes necesidades de UI (RxJS) y servidor (Reactive Streams).

Stack Tecnológico por Capa ​

Reactive Programming forma un stack tecnológico que abarca desde la capa UI hasta la capa de datos.

Arquitectura General ​

1. Capa UI (Frontend) ​

Tecnologías principales: RxJS, Signals

typescript
// RxJS (estándar de capa UI)
import { fromEvent } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs';

const searchInput$ = fromEvent(input, 'input').pipe(
  map(event => (event.target as HTMLInputElement).value),
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(query => fetch(`/api/search?q=${query}`).then(r => r.json()))
);

searchInput$.subscribe(results => updateUI(results));

Características:

  • Opera en entorno navegador
  • Prioriza capacidad de respuesta de UI
  • Maneja entrada de usuario, eventos DOM, comunicación HTTP de manera unificada

2. Capa Servidor (Backend) ​

Tecnologías principales: Project Reactor, Vert.x, Akka Streams

Project Reactor (Spring WebFlux) ​

java
// Project Reactor (estándar de capa servidor)
@RestController
public class UserController {

    @GetMapping("/users")
    public Flux<User> getUsers() {
        return userRepository.findAll()  // Reactive Repository
            .filter(user -> user.isActive())
            .map(user -> enrichUserData(user))
            .onErrorResume(error -> Flux.empty());
    }

    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userRepository.findById(id)
            .switchIfEmpty(Mono.error(new UserNotFoundException(id)));
    }
}

Características:

  • Cumple con estándar Reactive Streams
  • I/O no bloqueante
  • Alto throughput
  • Control de backpressure explícito

Akka Streams (Modelo Actor) ​

scala
// Akka Streams (para sistemas distribuidos)
val source = Source(1 to 100)
val flow = Flow[Int].map(_ * 2)
val sink = Sink.foreach[Int](println)

source.via(flow).to(sink).run()

Características:

  • Basado en modelo Actor
  • Óptimo para sistemas distribuidos
  • Aislamiento y recuperación de fallos

3. Capa Datos (Stream Processing) ​

Tecnologías principales: Kafka, Flink, Apache Beam

Apache Kafka (Event Streaming) ​

java
// Kafka Streams (pipeline de datos)
StreamsBuilder builder = new StreamsBuilder();

KStream<String, String> source = builder.stream("input-topic");

source
    .filter((key, value) -> value.length() > 10)
    .mapValues(value -> value.toUpperCase())
    .to("output-topic");

KafkaStreams streams = new KafkaStreams(builder.build(), config);
streams.start();

Características:

  • Plataforma de streaming de eventos distribuida
  • Alto throughput, baja latencia
  • Base para Event Sourcing y patrones CQRS
java
// Flink (procesamiento de flujos en tiempo real)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

DataStream<Event> events = env.addSource(new KafkaSource<>());

events
    .filter(event -> event.getValue() > threshold)
    .keyBy(Event::getSensorId)
    .timeWindow(Time.seconds(10))
    .reduce((e1, e2) -> new Event(e1.getSensorId(), e1.getValue() + e2.getValue()))
    .addSink(new DatabaseSink());

env.execute("Stream Processing Job");

Características:

  • Motor de procesamiento de flujos en tiempo real
  • Garantía exactamente una vez (Exactly-once)
  • Procesamiento de tiempo de evento y watermarks

Tecnologías de Puente: Cooperación entre Capas ​

¿Cómo cooperar entre diferentes stacks tecnológicos?

1. Capa UI ⇄ Capa Servidor: WebSocket / SSE ​

WebSocket (Comunicación Bidireccional) ​

Frontend (RxJS):

typescript
import { webSocket } from 'rxjs/webSocket';

const socket$ = webSocket<Message>('wss://example.com/ws');

// Recepción
socket$.subscribe(message => console.log('Recibido:', message));

// Envío
socket$.next({ type: 'subscribe', channel: 'notifications' });

Backend (Spring WebFlux):

java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").withSockJS();
    }
}

@Controller
public class NotificationController {

    @MessageMapping("/subscribe")
    public Flux<Notification> subscribe(@Payload SubscribeRequest request) {
        return notificationService.getNotificationStream(request.getChannel());
    }
}

Server-Sent Events (Servidor → Cliente) ​

Frontend (RxJS):

typescript
import { Observable } from 'rxjs';

function fromSSE<T>(url: string): Observable<T> {
  return new Observable(subscriber => {
    const eventSource = new EventSource(url);

    eventSource.onmessage = event => {
      subscriber.next(JSON.parse(event.data));
    };

    eventSource.onerror = error => {
      subscriber.error(error);
    };

    return () => eventSource.close();
  });
}

const notifications$ = fromSSE<Notification>('/api/notifications/stream');
notifications$.subscribe(n => console.log('Notificación:', n));

Backend (Spring WebFlux):

java
@GetMapping(value = "/notifications/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Notification> streamNotifications() {
    return notificationService.getNotificationStream()
        .delayElements(Duration.ofSeconds(1));
}

2. Capa Servidor ⇄ Capa Datos: Kafka Connect ​

Capa Servidor (Reactor) a Kafka:

java
// Spring WebFlux + Kafka
@Service
public class EventPublisher {

    private final KafkaTemplate<String, Event> kafkaTemplate;

    public Mono<Void> publishEvent(Event event) {
        return Mono.fromFuture(
            kafkaTemplate.send("events-topic", event.getId(), event)
                .completable()
        ).then();
    }
}

Kafka a Capa Servidor (Reactor):

java
// Kafka Consumer (Reactive)
@Service
public class EventConsumer {

    @KafkaListener(topics = "events-topic")
    public Mono<Void> consume(Event event) {
        return processEvent(event)
            .then();
    }

    private Mono<Void> processEvent(Event event) {
        return Mono.fromRunnable(() -> {
            System.out.println("Procesando evento: " + event);
        });
    }
}

3. Pipeline Reactivo End-to-End ​

Guía de Selección de Tecnología ​

¿Qué tecnología usar en qué capa?

Selección de Capa UI (Frontend) ​

RequisitoTecnología RecomendadaRazón
Flujo asíncrono complejoRxJSOperadores ricos, API unificada
Reactividad simpleAngular Signals / Solid.jsBajo costo de aprendizaje, intuitivo
Estándar de frameworkReact Hooks / Vue Composition APIIntegración con framework
typescript
// Elegir RxJS: flujo asíncrono complejo
combineLatest([
  userInput$,
  apiStatus$,
  validation$
]).pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(([input, status, isValid]) =>
    isValid ? submitData(input) : of(null)
  )
).subscribe(/*...*/);

// Elegir Signals: gestión de estado simple
const count = signal(0);
const doubled = computed(() => count() * 2);

Selección de Capa Servidor (Backend) ​

RequisitoTecnología RecomendadaRazón
API de alto throughputSpring WebFlux (Reactor)Ecosistema JVM, bibliotecas ricas
MicroserviciosVert.x / Quarkus (Mutiny)Ligero, inicio rápido
Sistemas distribuidosAkka StreamsModelo Actor, aislamiento de fallos
Entorno Node.jsRxJS / Node.js StreamsJavaScript unificado
java
// Spring WebFlux (API de alto throughput)
@GetMapping("/users")
public Flux<User> getUsers() {
    return userRepository.findAll()
        .filter(User::isActive)
        .map(this::enrichData);
}

Selección de Capa Datos (Stream Processing) ​

RequisitoTecnología RecomendadaRazón
Event streamingApache KafkaAlto throughput, distribuido, persistencia
Procesamiento en tiempo realApache FlinkBaja latencia, gestión de estado
Integración batch+streamApache BeamModelo API unificado
Procesamiento de datos IoTKafka + FlinkEscalabilidad, confiabilidad
java
// Kafka + Flink (procesamiento de datos IoT)
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

DataStream<SensorReading> sensorData = env
    .addSource(new FlinkKafkaConsumer<>("sensors", deserializer, properties));

sensorData
    .keyBy(SensorReading::getSensorId)
    .timeWindow(Time.minutes(5))
    .aggregate(new AverageAggregator())
    .addSink(new FlinkKafkaProducer<>("aggregated-data", serializer, properties));

Similitudes y Diferencias en Sintaxis de Operadores ​

RxJS, Reactor, Kafka Streams tienen sintaxis similar pero semántica diferente.

Similitudes: Pipeline Declarativo ​

RxJS (Capa UI):

typescript
source$
  .pipe(
    filter(x => x > 10),
    map(x => x * 2),
    take(5)
  )
  .subscribe(console.log);

Reactor (Capa Servidor):

java
source
    .filter(x -> x > 10)
    .map(x -> x * 2)
    .take(5)
    .subscribe(System.out::println);

Kafka Streams (Capa Datos):

java
stream
    .filter((key, value) -> value > 10)
    .mapValues(value -> value * 2)
    .to("output-topic");

Diferencias: Modelo de Ejecución y Semántica ​

AspectoRxJSReactorKafka Streams
Entorno de ejecuciónSingle-threaded (event loop)Multi-threaded posibleProcesamiento distribuido
SchedulingImplícito (sync por defecto)Explícito (Schedulers)Distribución automática
Manejo de errorescatchErroronErrorResumeGarantía Exactly-once
BackpressureNivel de operadorrequest(n)Control automático

WARNING

Solo porque la sintaxis de operadores sea similar no significa que funcionen igual. Es importante comprender el modelo de ejecución y semántica de cada tecnología.

Fortalezas y Áreas de Aplicación de RxJS ​

Áreas Donde RxJS es Más Fuerte ​

  1. Procesamiento UI del Navegador

    • Procesamiento unificado de entrada de usuario, eventos DOM, comunicación HTTP
  2. I/O Asíncrono de Node.js

    • Streaming de operaciones de archivos, comunicación de red
  3. Integración de Múltiples Procesos Asíncronos

    • Flujos complejos con combineLatest, merge, switchMap

Limitaciones de RxJS ​

  1. Procesamiento de Servidor de Alto Throughput

    • Reactor basado en JVM, Akka Streams son más ventajosos
  2. Procesamiento de Flujos Distribuidos

    • Kafka, Flink son más adecuados
  3. Control Estricto de Backpressure

    • Se necesita request(n) explícito del estándar Reactive Streams

TIP

RxJS es más fuerte en la capa UI, pero puede haber otras tecnologías más adecuadas en las capas servidor y datos. No es necesario usar RxJS en todas las capas.

Resumen ​

Posicionamiento de RxJS ​

IMPORTANT

RxJS es la principal implementación de Reactive Programming en entornos navegador y Node.js. Prioriza la capacidad de respuesta de la UI y adopta control de backpressure implícito.

Visión General del Ecosistema Reactive Streams ​

Capa UI         : RxJS, Signals
Capa Comunicación: WebSocket, SSE
Capa Servidor   : Reactor, Vert.x, Akka Streams
Capa Datos      : Kafka, Flink, Apache Beam

Guía de Selección de Tecnología ​

CapaTecnología RecomendadaRazón
Capa UIRxJS, SignalsCapacidad de respuesta UI, operadores ricos
Capa ServidorReactor, Vert.xAlto throughput, estándar Reactive Streams
Capa DatosKafka, FlinkProcesamiento distribuido, escalabilidad

Tecnologías de Puente ​

  • UI ⇄ Servidor: WebSocket, SSE
  • Servidor ⇄ Datos: Kafka Connect, Reactive Kafka

Similitudes en Sintaxis de Operadores ​

RxJS, Reactor, Kafka Streams tienen sintaxis similar pero modelo de ejecución y semántica diferente. Es importante comprender las características de cada tecnología y usarlas adecuadamente.

TIP

No es necesario unificar todas las capas con RxJS. Seleccionando la tecnología óptima para cada capa y cooperándolas con tecnologías de puente, se puede construir un sistema reactivo end-to-end.

Páginas Relacionadas ​

Referencias ​

Publicado bajo licencia CC-BY-4.0.