Modelos de programación reactiva mental para supervisores


Este artículo está dirigido a una amplia gama de lectores que desean saber qué es la programación reactiva. El propósito de este artículo es formar sus modelos mentales básicos de programación reactiva (MM RP) sin entrar en detalles técnicos.

Descargo de responsabilidad
( ) — , . , .
, : , . , , .
.

Pero primero, expliquemos qué tienen que ver los modelos mentales y los superiores mencionados en el encabezado del artículo ...

Sobre modelos mentales
, , , . , .
, , (. [1], [2])
? , , . (), , , . , , «», «» « » .
, , (), , - ().

Y aquí están los jefes ...
. «» «» , : . ( , «» , ).
, «» , , , , , . , . «» «». , , , .
, , , .., () — , , .
, .


¿Por qué la programación reactiva necesita su proyecto?


Muchas personas que no están familiarizadas con RP son inicialmente escépticas de él, sospechando que esta es solo otra moda vacía, cubierta con un par de hermosas palabras. Especialmente cuando aprenden que puedes evaluar el RP solo probándolo. Y probarlo es caro, debido al alto umbral de entrada. Vivimos y vivimos con OOP, ¿qué le falta?
Permítanme presentarles mi punto de vista sobre este tema.
En los albores de la programación, cuando la mayoría de los programas se escribían directamente en lenguaje ensamblador, el concepto de trabajo principal (un elemento del Modelo mental) de los programadores era una instrucción o un comando de lenguaje. Algunos datos (primitivos) se envían a la entrada de un comando o instrucción. La instrucción procesa y emite algunos datos de salida. La aparición de los primeros lenguajes de programación de procedimientos como Fortran no cambió la esencia del asunto. Solo los datos y las operaciones realizadas (como una secuencia de comandos elementales) se han vuelto más complicadas.
Con el tiempo, se hizo evidente que este concepto no es muy consistente con las realidades del mundo. Puede haber muchos datos, pueden ser difíciles de estructurar. Tanto los datos como la funcionalidad que los rodea sería bueno dividirlos en partes, desarrollarlos y mantenerlos por separado, y usarlos juntos.
OOP resolvió estos problemas de muchas maneras. La unidad del modelo mental de un programador típico de OOP es un objeto con datos ocultos (encapsulados) y una interfaz de acceso a estos datos como un conjunto de funciones.
OOP ha jugado un papel muy importante en la automatización e informatización de muchos procesos de fabricación y otros. Y junto con esto, sus debilidades fueron expuestas.
Desafortunadamente, en OOP no existe el concepto de un proceso como tal.
Intentaron mejorar la situación de diferentes maneras, concentrándose en varios aspectos.
De este modo, nacieron la Programación dirigida por eventos [3], la programación de flujo de datos [4], el procesamiento de flujo [5] y varios otros paradigmas.
Me aventuraría a despertar una corriente de críticas a los adherentes y expertos en estos paradigmas, tratando de transmitir en palabras simples su esencia general.
De una forma u otra, estos paradigmas operan con flujos de información. Al mismo tiempo, la programación dirigida por eventos, como su nombre lo indica, se centra en el proceso de aparición de elementos de flujo de información, programación de flujo de datos: control de flujo (división, fusión, transformación de flujos) y procesamiento de flujo en el uso óptimo de los recursos al procesar flujos.
La programación reactiva es casi lo mismo, pero con un enfoque en las operaciones elementales específicas de creación, administración y uso de hilos. Aquellos. RP describe cómo reacciona su sistema (el inglés reacciona) a los elementos del flujo de información. En este sentido, sería más correcto en ruso usar el término "Programación de reactivos" (de la palabra "reaccionar") o "Programación de reacción" (de la palabra "reacción a algo") si no fuera por cortar el oído, y el segundo no causó asociaciones incorrectas.
Me aventuraría a expresar otro pensamiento sedicioso. Lo que hoy llamamos en inglés Programación reactiva (Programación reactiva). llamado así por razones históricas e inclinado a favor de este término la opinión mayoritaria.
Este paradigma podría haber sido llamado de manera diferente. Por lo tanto, no se concentre en su nombre actual, sino intente comprender su esencia.
Y aunque hablaré sobre RP en un nivel bastante abstracto, citaré las API de la biblioteca RxJS como ejemplos concretos.
El acrónimo RxJS significa Reactive Extension for JavaScript, una extensión de JavaScript para las funciones de programación reactiva. Existen extensiones similares para muchos otros lenguajes de programación, como se puede ver en la imagen a continuación, tomada de [6].
Extensiones de programación reactiva

¿Por qué los modelos mentales de RP necesitan tu proyecto?


Los grandes proyectos no se hacen solos. A menudo puede leer o escuchar que los participantes del proyecto deben hablar el mismo idioma. Mi experiencia muestra que esto es apenas necesario y posible. Pero lo que se necesita es que los conceptos más básicos del proyecto sean establecidos y entendidos por los participantes del proyecto de la manera más equitativa posible. En términos de modelos mentales (MM), podemos decir que los MM de nivel superior deberían ser lo más similares posible.
Pero, ¿cómo pueden ser similares si los analistas piensan en términos de flujo de trabajo y casos de uso, arquitectos en patrones, desarrolladores en funciones y estructuras de datos y probadores en escenarios de prueba?
No insto a todos estos especialistas a comenzar a pensar al mismo tiempo con las categorías de Programación Reactiva, pero puedo prometerles que el conocimiento de estas categorías simplificará y aumentará la efectividad de su comunicación profesional con sus colegas. Esto debería suceder porque, por un lado, los MM RP tienen el poder necesario para describir flujos de trabajo complejos y, por otro lado, los MM RP se pueden convertir directamente a código en muchos lenguajes de programación.

Sorpresas, peligros o que en RP no es la forma en que todos estamos acostumbrados


Pero antes de entrar en la descripción de en qué consisten los modelos mentales de programación reactiva, según nuestra propia experiencia, me gustaría advertir al lector sobre lo que no está en ellos. Además, no solo no, sino que la expectativa de un comportamiento OOP simple y comprensible en el mundo lleva a tristes consecuencias.
Estoy haciendo esto no para intimidar, sino para intrigar al lector.

Diferencia 1: en lugar de un modelo de cursor, un gráfico computacional


Me aventuraré a sugerir que muchos lectores, al pensar en la próxima tarea que se realizará, tienen un modelo mental en su cabeza, que yo llamo el modelo de cursor. Se supone que se inventará un algoritmo paso a paso en forma de una lista lineal de instrucciones para resolver el problema. La ejecución del algoritmo se reduce a la ejecución paso a paso de las instrucciones una tras otra. Puede imaginar algo así como un puntero a la instrucción que se está ejecutando actualmente. Después de ejecutar la instrucción, el puntero (cursor) se mueve a la siguiente instrucción en la lista y comienza a ejecutarse.
Dentro de este modelo, una secuencia de comandos escritos en un lenguaje de programación orientado a objetos condicional
1. 1 = 2
2. 2 = 3 
3. 3 = 1 + 2
4.  1, 2, 3
5. 1 = 4
6.  1, 2, 3

dará el resultado
2 3 5
4 3 5

Nuestro modelo mental de cursor predice y explica perfectamente tal resultado. Después de procesar la tercera línea, se establece el valor X3 y el nuevo valor para X1 especificado en la línea 5 no puede cambiarlo.
En el mundo de RP, dependiendo de la interpretación de la operación "+", el resultado probablemente será este
2 3 5
4 3 7

En este mundo, la mayoría de las operaciones conectan los parámetros de entrada entre sí, creando así gráficos computacionales a través de los cuales los cálculos son "empujados" cuando se cambian uno o más parámetros.

Diferencia 2: operaciones asincrónicas


En el marco del modelo mental de cálculos del cursor, la siguiente operación no puede comenzar antes que la anterior.
Considere el siguiente ejemplo. Suponga que la función f1 calcula el salario base por el valor del identificador de usuario userId, y la función f2 calcula el bono basado en userId y el valor del salario.
Entonces el cálculo del salario completo puede verse así
1. X = f1(userId)
2. Y = f2(userId, X)
 X, Y

Supongamos que un miembro del personal tiene un salario base de 10,000. y una bonificación de 1000 unidades.
Nuestro modelo mental de cursor te dice qué imprimir.
10000 1000 

Por desgracia, en el mundo de la RP asincrónica, el resultado puede, dependiendo de la duración de las operaciones, ser
0 0 
10000 0 
0 1000 
10000 1000 

(Todavía no considero excepciones).
La cuestión es que en el mundo asíncrono reactivo, la siguiente operación no espera el final de la anterior, si es la anterior) asíncrona.
Para ilustrar esto, veamos algunos detalles importantes utilizando el ejemplo realista que se muestra en la figura a continuación.
La imagen muestra el tiempo de ejecución de cuatro instrucciones L1, L2, L3 y L4 que son independientes entre sí (sus números son importantes para nosotros, no la ortografía) en los modos sincrónico (parte superior de la imagen) y asíncrono (parte inferior de la imagen).

Como vemos, en el primer caso, cada instrucción posterior "espera" hasta el final de la anterior. En el caso asíncrono, todas las instrucciones comienzan a ejecutarse simultáneamente. Debido a la ejecución paralela y al uso de recursos, la mayoría de las instrucciones se ejecutan en modo asíncrono por más tiempo que en modo sincrónico. Sin embargo, juntos legarán su trabajo antes.
El orden de finalización de las instrucciones en ambos modos también es muy diferente. En sincronía:
L1, L2, L3, l4
pero en asíncrono:
L3, L2, L1, L4
.

Diferencia 3: las cadenas incompletas (sin consumidor) no funcionan en absoluto


En muchos lenguajes de programación tradicionales, es común asociar llamadas a funciones o propiedades de objetos con puntos.
Por ejemplo, la siguiente cadena de llamada de función de JavaScript convierte la palabra "bueno" en "perro":
„good“.split(„“).reverse().join(„“).replace(„oo“, „o“);

Las secuencias (cadenas) pueden ser largas. Por razones de reutilización o conveniencia, se pueden cortar en pedazos y realizar parcialmente.
Dividir una cadena en RP en dos partes y llamar solo una de ellas generalmente conduce a una falta de resultados, ya que solo se realiza la cadena completa (con el consumidor al final).

¿Por qué todo esto?


Probablemente, muchos lectores ya se hacen la pregunta: "¿No se han vuelto locos colectivamente, estos programadores reactivos? ¿Por qué se necesita, tal programación?
No pretendo predecir lo que los creadores y expertos de la República de Polonia responderían a esta pregunta, pero mi respuesta es esta: dicha programación es necesaria, porque muchos objetos del mundo real se comportan así.
Gráficos de computación : en esto se basa Excel, desde el cual no solo los contadores, sino también los gerentes de proyecto están encantados.
Operaciones asincrónicas . Cuando haces café o té en tu cocina, ¿te paras en la cocina todo este tiempo y miras tu cafetera o tetera? No. El dispositivo hierve agua y hace su trabajo, mientras que usted está haciendo otra cosa por ahora.
Cadena completa de operaciones.Intente desconectar la lámpara de su escritorio de la toma de corriente y presione el interruptor. La lámpara no se enciende de esto. Este objeto funciona solo si hay una cadena completa, desde la fuente hasta el consumidor de electricidad. Y hay muchos, si no la mayoría, de tales objetos en el mundo real.

Quiero tranquilizarlo, su conocimiento de la programación tradicional y el cursor MM no deben tirarse a la basura debido a la aparición de RP. La programación reactiva los dejó solos y los expandió con nuevas operaciones en nuevos tipos de objetos. Cómo, hablaremos de esto más tarde.

El espacio de programación de modelos mentales y el lugar de MM RP en él


Al hablar sobre el lugar de la RP en el panorama general de la programación, los autores a menudo mencionan dos dimensiones: la complejidad de los objetos procesados ​​y el sincronismo / asincronía de las operaciones. Un ejemplo de dicha clasificación se puede encontrar en el libro "RxJS en acción" [7], en el capítulo "Cuándo y dónde usar RxJS".
En esta clasificación, la dimensión de los objetos se divide en objetos individuales y multi-objetos: matrices, listas, etc. Las operaciones se dividen en síncronas y asíncronas.
Por lo tanto, esta clasificación divide el mundo de la programación en cuatro áreas. RP es una de estas áreas responsables del procesamiento de objetos múltiples con operaciones asincrónicas.
Esta clasificación me parece muy interesante, pero me gustaría verla desde el punto de vista de los modelos mentales. La siguiente tabla los presenta.
Valores únicos y objetos, ,
, (Stream)
, (Promise)(Workflow)

Suponemos que los modelos mentales de instrucciones y el cursor no requieren más explicaciones.
El ciclo es una extensión de las instrucciones MM y el cursor debido a las instrucciones adicionales del ciclo o regreso a algún punto. Esto permite un conjunto de instrucciones de procesamiento para que un solo objeto se "ajuste" en un bucle y, por lo tanto, procese muchos de estos objetos. En este caso, el cursor se mueve dentro del ciclo como en el modelo anterior, y al llegar al punto de transición, salta al principio o el procesamiento del ciclo se detiene si se procesan todos los objetos.
Chorro. La diferencia entre este modelo mental y el anterior es que el cursor que
apunta al objeto procesado permanece en su lugar, y los objetos mismos lo "recorren".
Miremos esto con dos ejemplos. Si pinta una cerca de madera, usted, por analogía con el modelo de cursor, va de tabla en tabla. Pero el trabajador en el transportador permanece en su lugar y, por analogía con el modelo de chorro, las piezas a procesar se acercan a él. A menudo se hace referencia a estos objetos con el término English Stream, por ejemplo, en el lenguaje Java.
Semáforo. Este MM es más fácil de asociar con un semáforo en una intersección. Los objetos asincrónicos sondean periódicamente el estado de una variable pública y realizan ciertas acciones después de cambiar su estado. (como los conductores frente a un semáforo)
Esperando.Una metáfora adecuada para este modelo de expectativa mental es la carta en papel o Emall que esperaba la última vez que obtuvo su trabajo. Puede haber una respuesta positiva o negativa. Su comportamiento después de recibir la carta depende mucho de su contenido. El término en inglés Promise se usa a menudo para describir este tipo de objetos. Eso, desde el punto de vista del usuario, es una expectativa, para el contratista que brinda los servicios, es más bien una promesa.
Como vemos en la descripción, el movimiento a lo largo de cada dimensión (de arriba a abajo o de derecha a izquierda en la tabla) significa un cambio cualitativo en el Modelo Mental.
Como se puede ver en la tabla, los aviones y las expectativas son vecinos a la izquierda y en la parte superior de la celda del sudeste que nos interesa. ¿Qué hay de nuevo en los modelos mentales de flujos que habitan en la célula que nos interesa en comparación con ellos?

¿Cuál es la extensión?


La expansión de Streams en comparación con Expectations es que la información esperada puede llegar no una vez, sino en muchas partes. En este caso, el proceso puede finalizar sin finalizar. Aquellos. Después de una serie de porciones exitosas, recibiremos una notificación de error. Además, se agrega otra versión de la información: una notificación del final del proceso.
Esto significa, por ejemplo, que es posible recibir varias (pero no todas) partes de la información esperada y (sin un mensaje de error) un mensaje sobre el final del procesamiento.
Recuerde nuevamente, con Waiting, solo tenemos dos opciones alternativas para la información resultante.
El modelo de chorro mental es muy adecuado para comprender, discutir e implementar el proceso de transformación de una secuencia de objetos del mismo tipo. MM Stream lo expande con los siguientes aspectos:
  • Puede haber muchos aviones y podemos fusionarlos
  • Los chorros pueden ser heterogéneos.
  • Podemos dividir los jets en nuevos según diferentes criterios.
  • Podemos "cerrar" y / o transformarlos en otros nuevos dentro del marco de una secuencia.

Entonces, determinamos el lugar de MM RP (Streams) en el espacio o paisaje de los objetos de Programación. Ahora bajemos la vista de pájaro y echemos un vistazo más de cerca a Streams y sus Modelos Mentales.

Corrientes y fases de su ciclo de vida.


Como primera aproximación, los flujos de RP pueden imaginarse como el agua fluye en las tuberías de agua o la electricidad. Debe recordarse que, como cualquier otra analogía, esta analogía tiene sus límites y no siempre es aplicable.
Hablando sobre el flujo, se pueden distinguir los siguientes aspectos importantes:

  1. Cada hilo surge de alguna manera
  2. De alguna manera se está moviendo hacia el consumidor.
  3. Algo sucede en el camino con él (se transforma)
  4. Se puede dividir en varias transmisiones o fusionar con otras transmisiones.
  5. El consumidor de alguna manera usa el flujo, dejando de existir.

Los aspectos enumerados son fases simultáneas del ciclo de vida de elementos individuales del flujo.
Consideremos con más detalle el ejemplo de las funciones RxJS.

Creación de hilos


Las secuencias se pueden crear a partir de elementos pasivos como una matriz o una lista de objetos en su programa, bytes, líneas de archivo, etc. Este tipo de fuentes de flujo se llama frío (aunque técnicamente hay una definición más precisa de las fuentes de flujo frío).
Las llamadas aguas termales "viven sus propias vidas" y si no se conecta a tiempo, la información se perderá. Esta categoría incluye información sobre las acciones del usuario en una computadora, tableta, teléfono inteligente, por ejemplo, información sobre pulsaciones de teclas, movimientos del mouse o tocar la pantalla. También en esta categoría se encuentran los datos solicitados por varios protocolos como HTTP, datos de varios sensores.
Cabe señalar que hay los llamados manantiales "cálidos". Además, algunas fuentes "calientes" se pueden "enfriar" y las "frías" se pueden "calentar". Pero debería leer sobre esto en literatura especial, por ejemplo, en el libro [7].
Es importante para nosotros saber que todas las operaciones de creación de flujos crean objetos del mismo tipo, que pueden ser procesados ​​por las mismas operaciones, independientemente del contenido. En este artículo, llamamos a estos objetos flujos. El nombre en inglés correspondiente es Observable.

Movimiento del consumidor y transformación del flujo


Las operaciones de transformación de flujo pueden llevarse a cabo tanto para el consumidor como para él. En ambos casos, las operaciones de procesamiento de los elementos de flujo son estrictamente secuenciales, es decir la siguiente operación se inicia estrictamente solo después de que la anterior se haya completado y le haya pasado el resultado.
A diferencia de Stream, que en algunos lenguajes de programación son construcciones de lenguaje con su propia sintaxis y semántica, las extensiones reactivas como RxJS en JavaScript se ven obligadas a usar la sintaxis y la semántica básica de un lenguaje extensible. Por lo tanto, RxJs implementa la función pipe (), dentro de la cual puede realizar llamadas a funciones: manejadores tanto de la secuencia en sí como de sus elementos individuales.
Es importante tener en cuenta que solo las funciones especiales y canalizables pueden ser controladores de flujo.

"Flujo trifásico"


Si continuamos la analogía con la electricidad, entonces los flujos que estamos considerando pueden llamarse trifásicos. Junto con el "cable normal" que transmite la información básica, también hay un "cable de error" y un "cable de terminación de flujo". Las operaciones de transformación permiten no solo cambiar el objeto, sino también redirigirlo a otro "cable". Esta técnica se utiliza, por ejemplo, al procesar supuestos errores al trabajar con servidores que utilizan el protocolo HTTP. Por ejemplo, si un servidor no responde, puede intentar solicitar otro sin informar al usuario sobre la falla al solicitar el primer servidor.
Este es otro elemento muy importante de su modelo de flujo mental. Si en los paradigmas de programación tradicionales el error se devuelve desde la función de procesamiento como un código de error o debe ser interceptado como una interrupción (excepción), entonces en los flujos el error "fluye" independientemente del canal principal.
Allí puede ser procesado. Por ejemplo, si un usuario ha ingresado una contraseña incorrecta, se le puede dar una oportunidad adicional de intentar ingresarla una o más veces.

División y fusión de flujos


La división de flujos se lleva a cabo en dos etapas. En la primera etapa, se inician los hilos vacíos. Luego, en la segunda etapa (etapa de procesamiento de flujo), en una de las funciones de procesamiento, los elementos serán analizados y redirigidos al flujo deseado. Técnicamente, hay muchas opciones sobre cómo hacer esto. Por ejemplo, eliminarlo del hilo actual o clonarlo antes de comenzarlo en un nuevo hilo.
Puede fusionar múltiples transmisiones en una de una sorprendente cantidad de formas.
Las formas más simples que vienen a la mente son fusionarlas en el orden de recepción, o primero todo desde la primera secuencia y luego todo desde la segunda.
El método que se muestra a continuación en la imagen permite que una de las dos corrientes forme una que contenga pares ordenados de objetos del primer y segundo flujo. En este caso, se forma un nuevo par si aparece un nuevo elemento en uno de los flujos. A contiene un par de estrictamente los últimos elementos de cada secuencia. Esto lleva al hecho de que el mismo elemento puede incluirse en varios pares.
La notación gráfica utilizada en este ejemplo se llama diagramas de mármol y es muy efectiva para explicar la semántica de los flujos de división y fusión.
Si este tema le interesa, le aconsejo que estudie las operaciones y sus diagramas de mármol en el recurso [8].


Usar corrientes


Para utilizar los elementos de la secuencia, el usuario o el cliente primero deben suscribirse a ella. Como regla general, al final del procesamiento, debe darse de baja, ya que los recolectores de basura no siempre pueden desactivar automáticamente una suscripción cuando intentan utilizar un suscriptor.
Muchos clientes pueden suscribirse a un hilo. En RxJs, la función de suscripción se llama subscribe (). En él, en la mayoría de los casos, es aconsejable realizar llamadas de procesamiento de los elementos "normales" del flujo, un controlador de errores y (relativamente raramente) un controlador de terminación del flujo.
Cada uno de los suscriptores de la secuencia recibe su copia del elemento o un clon del elemento original. Para no causar problemas, la secuencia se implementa de tal manera que los elementos recibidos para el procesamiento se vuelven inmutables. En algunas situaciones, esta limitación aún se puede eludir, pero es mejor no hacerlo.

Peligroso encanto de las corrientes


Las corrientes son objetos muy complicados, algo similares a las integrales en matemáticas. Una cosa es saber que existen e incluso imaginar más o menos lo que es, y otra muy distinta poder usarlos.
La comprensión de la lógica interna de su funcionamiento, necesaria para aplicarlos bien en la práctica, requiere un esfuerzo intelectual considerable.
Las secuencias están intrínsecamente relacionadas con la programación funcional. Para el uso competente de los flujos, es útil comprender cómo es posible construir y aplicar funciones de segundo orden, funciones para las cuales otras funciones sirven como argumentos.
Entonces la belleza y la elegancia de los flujos se te revelarán por completo.
Las corrientes son contagiosas. Después de comprender su belleza, quiero usarlos en todas las tareas, lo que por supuesto no es necesario.
En qué tareas es aconsejable usar flujos, y donde se deben usar los métodos tradicionales, todos deciden por sí mismos.

Resumir


En este artículo intenté contarte sobre los Modelos Mentales de Programación Reactiva (MM RP) e incluso ponerlos parcialmente en tu Conciencia. Repitamos los puntos principales nuevamente.

  1. MM RP son especiales, no similares a los modelos mentales de la programación tradicional.
  2. Al embarcarse en la Programación Reactiva, uno debe recordar que algunos bien establecidos en otras áreas de MM como un cursor, cadenas de llamadas o bucles no funcionan, o no funcionan así.
  3. El modelo mental principal de RP es un "flujo de tres canales" con un canal para elementos "normales", errores e información sobre el final del flujo.
  4. Las corrientes pueden ser finitas e infinitas.
  5. «», «» «» . «» «».
  6. . (, ). .
  7. , .
  8. , .
  9. . .
  10. , «».


Si está interesado en este tema, puede "jugar" con transmisiones utilizando los simuladores disponibles en el sitio [8].
Si desea comprender mejor los conceptos de RP, le recomiendo que trabaje en el libro [7] y, por supuesto, que se familiarice con El Manifiesto Reactivo [11].
Alcanzará el siguiente nivel en la formación de su propio MM RP trabajando a través de los libros [9] y [10] sobre el diseño y modelado de sistemas reactivos.

Literatura y referencias


  1. La programación es la materialización de ideas. (Artículo en Habr. Habr.com/ru/post/425321 )
  2. Sirotin V. RPSE: Reificación como paradigma de la ingeniería de software. arxiv.org/abs/1810.01904
  3. Programación dirigida por eventos. en.m.wikipedia.org/wiki/Event-driven_programming
  4. Dataflow-programming. en.m.wikipedia.org/wiki/Dataflow_programming
  5. Stream-processing. en.m.wikipedia.org/wiki/Stream_processing
  6. Rx-Extensions: reactivex.io/languages.html
  7. RxJS in Action. – 4. August 2017. Paul P. Daniels (Autor), Luis Atencio. Manning Publications. ISBN-13: 978-1617293412
  8. RxJS online Documentstion. xgrommx.imtqy.com/rx-book/index.html
  9. Reactive Design Patterns. 2017. Roland Kuhn Dr., Brian Hanafee, Jamie Allen. Manning Publications. ISBN-13: 978-1617291807
  10. Functional and Reactive Domain Modeling. 2016. Debasish Ghosh.Manning Publications. ISBN-13: 978-1617292248
  11. The Reactive Manifesto www.reactivemanifesto.org


: geralt

Source: https://habr.com/ru/post/undefined/


All Articles