Construyendo una estrategia anticorrosión front-end basada en Observable

Introducción: El ciclo de vida y la iteración del negocio To B suele durar muchos años. Con la iteración y evolución del producto, las relaciones front-end y back-end centradas en las llamadas de interfaz se volverán muy complejas. Después de muchos años de iteraciones, cualquier modificación en la interfaz puede causar problemas imprevistos al producto. En este caso, se vuelve muy importante construir una aplicación front-end más robusta para garantizar la robustez y escalabilidad del front-end en iteraciones a largo plazo. Este artículo se centrará en cómo usar las estrategias anticorrosivas de la interfaz para evitar o reducir el impacto de los cambios en la interfaz en la interfaz.

Autor | Xie Yadong
Fuente | Alibaba Technology Cuenta pública

El ciclo de vida y la iteración del negocio To B suele durar muchos años. Con la iteración y la evolución del producto, las relaciones de front-end y back-end centradas en las llamadas de interfaz se volverán muy complicadas. Después de muchos años de iteraciones, cualquier modificación en la interfaz puede causar problemas imprevistos al producto. En este caso, se vuelve muy importante construir una aplicación front-end más robusta para garantizar la robustez y escalabilidad del front-end en iteraciones a largo plazo. Este artículo se centrará en cómo usar las estrategias anticorrosivas de la interfaz para evitar o reducir el impacto de los cambios en la interfaz en la interfaz.

dilema y dilema

Para explicar los problemas que enfrenta el front-end con mayor claridad, tomamos como ejemplo la página del tablero común en el negocio To B. Esta página contiene tres partes de visualización de información: memoria disponible, memoria utilizada y proporción de memoria utilizada.

En este momento, las dependencias entre los componentes frontales y las interfaces se muestran en la siguiente figura.

Cuando la interfaz devuelve el ajuste de estructura, se debe ajustar la forma en que el componente MemoryFree llama a la interfaz. Del mismo modo, MemoryUsage y MemoryUsagePercent deben modificarse para que funcionen.

Puede haber cientos de interfaces a las que se enfrenta el negocio To B real, y la lógica de integración de componentes e interfaces es mucho más complicada que los ejemplos anteriores.

Después de varios años o incluso iteraciones más largas, la interfaz generará gradualmente varias versiones.Teniendo en cuenta la estabilidad de la interfaz y los hábitos de los usuarios, el front-end a menudo se basa en varias versiones de la interfaz para construir la interfaz al mismo tiempo. Cuando algunas interfaces deben ajustarse fuera de línea o cambiarse, el front-end debe volver a comprender la lógica comercial y realizar muchos ajustes de lógica de código para garantizar el funcionamiento estable de la interfaz.

Los cambios de interfaz comunes que afectan al front-end incluyen, entre otros, los siguientes:

  • ajuste del campo de retorno
  • cambio de llamada
  • Coexistencia de múltiples versiones.

Cuando el front-end se enfrenta a un negocio basado en plataformas, estos problemas se vuelven más difíciles. Los productos de la plataforma encapsularán uno o más motores subyacentes. Por ejemplo, las plataformas de aprendizaje automático pueden construirse sobre la base de motores de aprendizaje automático como TensorFlow y Pytorch, y las plataformas informáticas en tiempo real pueden construirse sobre la base de motores informáticos como Flink y Spark.

Aunque la plataforma encapsulará la mayoría de las interfaces del motor en la capa superior, es inevitable que algunas interfaces de bajo nivel se transmitan de forma transparente y directa al front-end. En este momento, el front-end no solo tiene que lidiar con la cambios en la interfaz de la plataforma, pero también se enfrenta al motor de código abierto Desafíos que plantean los cambios en la interfaz.

La situación a la que se enfrenta el front-end está determinada por las relaciones únicas entre el front-end y el back-end. A diferencia de otros campos, en el negocio To B, el front-end generalmente acepta el suministro del proveedor back-end como un cliente intermedio y, en algunos casos, se convierte en un seguidor del back-end.

En la relación cliente/proveedor, el front-end es descendente y el equipo de back-end es upstream. El contenido de la interfaz y el tiempo de lanzamiento generalmente los determina el equipo de back-end.

En la relación de seguidor, el equipo de back-end ascendente no realizará ningún ajuste de acuerdo con las necesidades del equipo de front-end, y el front-end solo puede ajustarse a los modelos de upstream y back-end. Esto suele suceder cuando el front-end no puede ejercer influencia en el equipo back-end ascendente, como cuando el front-end necesita diseñar la interfaz en función de la interfaz del proyecto de código abierto, o cuando el modelo del back-end El equipo es muy maduro y difícil de modificar.

El autor de Clean Architecture ha descrito un dilema de diseño de arquitectura integrado, que es muy similar al dilema que describimos anteriormente.

Se supone que el software es algo de larga duración, y el firmware se volverá obsoleto a medida que el hardware evolucione, pero la realidad es que, si bien el software en sí no se desgasta con el tiempo, el hardware y su firmware sí lo hacen. cambiarse en consecuencia.

Ya sea una relación cliente/proveedor o una relación de seguidor, así como el software no puede determinar el desarrollo y la iteración del hardware, es difícil o imposible que el front-end determine el diseño del motor y la interfaz, aunque el front-end en sí sí lo hace. no cambiará con el tiempo No está disponible, pero el motor de tecnología y las interfaces relacionadas quedarán obsoletas con el tiempo, y el código front-end se pudrirá gradualmente con el reemplazo iterativo del motor de tecnología, y finalmente se verá obligado a reescribirse.

Diseño de dos capas anticorrosión

Antes del nacimiento de Windows, los ingenieros introdujeron el concepto de HAL (capa de abstracción de hardware) para resolver los problemas de mantenimiento de hardware, firmware y software antes mencionados.HAL proporciona servicios para el software y protege los detalles de implementación del hardware, de modo que el software no tiene que modificaciones frecuentes debido a cambios de hardware o firmware.

La idea de diseño de HAL también se llama Capa Anticorrupción en Diseño Dirigido por Dominio (DDD). Entre los diversos mapeos de contexto definidos por DDD, la capa anticorrupción es la más defensiva. A menudo se usa cuando el equipo de aguas abajo necesita evitar preferencias técnicas externas o la intrusión del modelo de dominio, y puede ayudar a aislar bien el modelo de aguas arriba del modelo de aguas abajo.

Podemos introducir el concepto de capa anticorrosión en el front-end para reducir o evitar el impacto de los cambios en la interfaz de mapeo de contexto del back-end actual en el código del front-end.

Hay muchas formas de implementar capas anticorrosión en la industria. Tanto GraphQL como BFF, que han sido populares en los últimos años, se pueden usar como alternativas, pero la selección de tecnología también está limitada por los escenarios comerciales. Completamente diferente del negocio To C, en el negocio To B, la relación entre el front end y el back end suele ser una relación cliente/proveedor o seguidor/seguidor. Bajo esta relación, se ha vuelto poco realista esperar que el back-end coopere con el front-end para transformar la interfaz con GraphQL, y la construcción de BFF generalmente requiere recursos de implementación y costos de operación y mantenimiento adicionales.

En los casos anteriores, construir una capa anticorrosión en el lado del navegador es una solución más factible, pero construir una capa anticorrosión en el navegador también enfrenta desafíos.

Ya sea React, Angular o Vue, existen innumerables soluciones de capa de datos, desde Mobx, Redux, Vuex, etc., estas soluciones de capa de datos en realidad invadirán la capa de visualización. ¿Existe una solución de capa anticorrosión que se pueda combinar con la capa de vista?¿Desacoplamiento completo? La solución Observable representada por RxJS puede ser la mejor opción en este momento.

RxJS es la implementación de JavaScript del proyecto ReactiveX, que originalmente era una extensión de LINQ y fue desarrollado por un equipo dirigido por el arquitecto de Microsoft Erik Meijer. El objetivo de este proyecto es proporcionar una interfaz de programación consistente para ayudar a los desarrolladores a manejar flujos de datos asincrónicos más fácilmente. En la actualidad, RxJS se usa a menudo como una herramienta de desarrollo de programación reactiva en el desarrollo, pero en el escenario de construir una capa anticorrosión, la solución Observable representada por RxJS también puede desempeñar un papel muy importante.

Elegimos RxJS principalmente en base a las siguientes consideraciones:

  • La capacidad de unificar diferentes fuentes de datos: RxJS puede convertir websockets, solicitudes http e incluso acciones de usuario, clics en páginas, etc. en un objeto Observable unificado.
  • La capacidad de unificar diferentes tipos de datos: RxJS unifica datos asíncronos y síncronos en objetos observables.
  • Amplias capacidades de procesamiento de datos: RxJS proporciona una gran cantidad de operadores, que pueden preprocesar los Observables antes de suscribirse.
  • No invade la arquitectura front-end: el Observable de RxJS se puede convertir hacia y desde Promise, lo que significa que todos los conceptos de RxJS se pueden encapsular por completo en la capa de datos, y solo Promise se puede exponer a la capa de vista.

Cuando se introduzca RxJS para convertir todos los tipos de interfaces en objetos Observables, los componentes de la vista frontal solo se basarán en Observables y se desvincularán de los detalles de la implementación de la interfaz. Al mismo tiempo, los Observables se pueden convertir hacia y desde Promesas, y lo que se obtiene en la capa de vista es puro Las promesas se pueden usar con cualquier esquema y marco de capa de datos.

Además de convertirse a Promise, los desarrolladores también pueden combinar soluciones RxJS en la capa de representación, como rxjs-hooks, para una mejor experiencia de desarrollo.

Implementación de tres capas anticorrosivas

En referencia al diseño de la capa anticorrosión anterior, implementamos el código de la capa anticorrosión con RxJS Observable como núcleo en el proyecto del tablero al principio.

El código central de la capa anticorrosión es el siguiente

export function getMemoryFreeObservable(): Observable<number> {
  return fromFetch("/api/v1/memory/free").pipe(mergeMap((res) => res.json()));
}

export function getMemoryUsageObservable(): Observable<number> {
  return fromFetch("/api/v1/memory/usage").pipe(mergeMap((res) => res.json()));
}

export function getMemoryUsagePercent(): Promise<number> {
  return lastValueFrom(forkJoin([getMemoryFreeObservable(), getMemoryUsageObservable()]).pipe(
    map(([usage, free]) => +((usage / (usage + free)) * 100).toFixed(2))
  ));
}

export function getMemoryFree(): Promise<number> {
  return lastValueFrom(getMemoryFreeObservable());
}

export function getMemoryUsage(): Promise<number> {
  return lastValueFrom(getMemoryUsageObservable());
}

El código de implementación de MemoryUsagePercent es el siguiente: en este momento, el componente ya no dependerá de la interfaz específica, sino que dependerá directamente de la implementación de la capa anticorrosión.

function MemoryUsagePercent() {
  const [usage, setUsage] = useState<number>(0);
  useEffect(() => {
    (async () => {
      const result = await getMemoryUsagePercent();
      setUsage(result);
    })();
  }, []);
  return <div>Usage: {usage} %</div>;
}

export default MemoryUsagePercent;

1 Ajuste del campo de retorno

Cuando se cambia el campo de retorno, la capa anticorrosión puede interceptar efectivamente el impacto de la interfaz en el componente.Cuando los datos de retorno de /api/v2/quota/free y /api/v2/quota/usage se cambian al siguiente estructura

{
  requestId: string;
  data: number;
}

Solo necesitamos ajustar las dos líneas de código de la capa anticorrosión. Tenga en cuenta que en este momento, el getMemoryUsagePercent empaquetado por nuestra capa superior se basa en Observable, por lo que no se requieren cambios.

export function getMemoryUsageObservable(): Observable<number> {
  return fromFetch("/api/v2/memory/free").pipe(
     mergeMap((res) => res.json()),
+    map((data) => data.data)
  );
}

export function getMemoryUsageObservable(): Observable<number> {
  return fromFetch("/api/v2/memory/usage").pipe(
     mergeMap((res) => res.json()),
+    map((data) => data.data)
  );
}

En la capa anticorrosión Observable, habrá dos diseños de Observable de alto nivel y Observable de bajo nivel. En el ejemplo anterior, Free Observable y Usage Observable son encapsulación de bajo nivel, mientras que Percent Observable usa Free y Usage Observable para alta -diseño de nivel. Encapsulación de alto nivel. Cuando se cambia la encapsulación de bajo nivel, debido a las características de Observable en sí, la encapsulación de alto nivel a menudo no necesita ser cambiada. Este es también un beneficio adicional que nos brinda el capa anticorrosión.

2 Se cambia el método de llamada

La capa anticorrosión también puede desempeñar un papel cuando cambia el método de invocación. /api/v3/memory devuelve directamente los datos de libre y uso. El formato de la interfaz es el siguiente.

{
  requestId: string;
  data: {
    free: number;
    usage: number;
  }
}

El código de la capa anticorrosión solo debe actualizarse de la siguiente manera para garantizar que no sea necesario modificar el código de la capa del componente.

export function getMemoryObservable(): Observable<{ free: number; usage: number }> {
  return fromFetch("/api/v3/memory").pipe(
    mergeMap((res) => res.json()),
    map((data) => data.data)
  );
}

export function getMemoryFreeObservable(): Observable<number> {
  return getMemoryObservable().pipe(map((data) => data.free));
}

export function getMemoryUsageObservable(): Observable<number> {
  return getMemoryObservable().pipe(map((data) => data.usage));
}

export function getMemoryUsagePercent(): Promise<number> {
  return lastValue(getMemoryObservable().pipe(
    map(({ usage, free }) => +((usage / (usage + free)) * 100).toFixed(2))
  ));
}

3 Coexistencia de múltiples versiones

Cuando el código front-end debe implementarse en varios entornos, la interfaz v3 está disponible en algunos entornos, mientras que solo la interfaz v2 está implementada en algunos entornos. En este momento, aún podemos proteger las diferencias ambientales en la protección contra la corrosión. capa.

export function getMemoryLegacyObservable(): Observable<{ free: number; usage: number }> {
  const legacyUsage = fromFetch("/api/v2/memory/usage").pipe(
    mergeMap((res) => res.json())
  );
  const legacyFree = fromFetch("/api/v2/memory/free").pipe(
    mergeMap((res) => res.json())
  );
  return forkJoin([legacyUsage, legacyFree], (usage, free) => ({
    free: free.data.free,
    usage: usage.data.usage,
  }));
}

export function getMemoryObservable(): Observable<{ free: number; usage: number }> {
  const current = fromFetch("/api/v3/memory").pipe(
    mergeMap((res) => res.json()),
    map((data) => data.data)
  );
  return race(getMemoryLegacyObservable(), current);
}

export function getMemoryFreeObservable(): Observable<number> {
  return getMemoryObservable().pipe(map((data) => data.free));
}

export function getMemoryUsageObservable(): Observable<number> {
  return getMemoryObservable().pipe(map((data) => data.usage));
}

export function getMemoryUsagePercent(): Promise<number> {
  return lastValue(getMemory().pipe(
    map(({ usage, free }) => +((usage / (usage + free)) * 100).toFixed(2))
  ));
}

A través del operador de carrera, cuando la interfaz de cualquier versión de v2 y v3 está disponible, la capa anticorrosión puede funcionar normalmente y la capa de componentes ya no necesita prestar atención al impacto de la interfaz por el medio ambiente.

Cuatro aplicaciones adicionales

La capa anticorrosión no es solo una capa adicional de encapsulación y aislamiento de la interfaz, sino que también tiene las siguientes funciones.

1 Mapas conceptuales

La semántica de la interfaz y la semántica de los datos requeridos por el front-end a veces no se corresponden completamente.Al llamar a la interfaz directamente en la capa de componentes, todos los desarrolladores deben saber lo suficiente sobre el mapeo semántico entre la interfaz y la interfaz. Con la capa anticorrosión, el método de llamada proporcionado por la capa anticorrosión contiene la semántica real de los datos, lo que reduce el costo de comprensión secundaria de los desarrolladores.

2 Adaptación de formato

En muchos casos, la estructura y el formato de datos devueltos por la interfaz no coinciden con el formato de datos requerido por el front-end. Al agregar lógica de conversión de datos a la capa anticorrosión, se puede reducir la intrusión de datos de interfaz en el código comercial. En el caso anterior, encapsulamos la devolución de datos de getMemoryUsagePercent, de modo que la capa del componente pueda usar directamente los datos porcentuales sin convertirlos nuevamente.

3 caché de interfaz

En el caso de que múltiples servicios dependan de la misma interfaz, podemos agregar lógica de almacenamiento en caché a través de la capa anticorrosión, lo que reduce efectivamente la presión de llamada de la interfaz.

De manera similar a la adaptación de formato, encapsular la lógica de almacenamiento en caché en la capa anticorrosión puede evitar el almacenamiento en caché secundario de datos por parte de la capa de componentes y puede administrar de forma centralizada los datos almacenados en caché para reducir la complejidad del código. A continuación se muestra un ejemplo de almacenamiento en caché simple.

class CacheService {
  private cache: { [key: string]: any } = {};
  getData() {
    if (this.cache) {
      return of(this.cache);
    } else {
      return fromFetch("/api/v3/memory").pipe(
        mergeMap((res) => res.json()),
        map((data) => data.data),
        tap((data) => {
          this.cache = data;
        })
      );
    }
  }
}

4 Fondo de estabilidad

Cuando la estabilidad de la interfaz es deficiente, la práctica habitual es lidiar con el error de respuesta en la capa de componentes.Este tipo de lógica suele ser complicada y el costo de mantenimiento de la capa de componentes será alto. Podemos verificar la estabilidad a través de la capa anticorrosión. Cuando la interfaz falla, podemos devolver los datos comerciales finales. Dado que los datos finales se mantienen uniformemente en la capa anticorrosión, las pruebas y modificaciones posteriores serán más conveniente. En la capa anticorrosión de coexistencia de múltiples versiones anterior, agregue el siguiente código, en este momento, incluso si las interfaces v2 y v3 no pueden devolver datos, el front-end aún puede permanecer disponible.

  return race(getMemoryLegacy(), current).pipe(
+   catchError(() => of({ usage: '-', free: '-' }))
  );

5 Depuración y pruebas conjuntas

La interfaz y el front-end pueden desarrollarse en paralelo. En este momento, no hay una interfaz de back-end real disponible para el desarrollo de front-end. En comparación con la forma tradicional de construir una API simulada, es más conveniente simular los datos directamente en la capa anticorrosión.

export function getMemoryFree(): Observable<number> {
  return of(0.8);
}

export function getMemoryUsage(): Observable<number> {
  return of(1.2);
}

export function getMemoryUsagePercent(): Observable<number> {
  return forkJoin([getMemoryUsage(), getMemoryFree()]).pipe(
    map(([usage, free]) => +((usage / (usage + free)) * 100).toFixed(2))
  );
}

Los datos simulados en la capa anticorrosión también se pueden usar para probar la página, como el impacto de simular una gran cantidad de datos en el rendimiento de la página.

export function getLargeList(): Observable<string[]> {
  const options = [];
  for (let i = 0; i < 100000; i++) {
    const value = `${i.toString(36)}${i}`;
    options.push(value);
  }
  return of(options);
}

cinco resumen

En este artículo cubrimos lo siguiente:

  1. ¿Cuáles son los dilemas y las razones por las que el front-end se enfrenta a frecuentes cambios de interfaz?
  2. Idea de diseño y selección de tecnología de capa anticorrosión.
  3. Ejemplo de código de implementación de una capa anticorrosión usando Observable
  4. Papel adicional de la capa anticorrosión

Los lectores deben tener en cuenta que es razonable introducir una capa anticorrosión en el front-end solo en escenarios específicos, es decir, el front-end está en una relación de seguidor o proveedor/cliente y se enfrenta a una gran cantidad de interfaces que no pueden garantizar la estabilidad y compatibilidad. Si la capa anticorrosión se puede construir en la puerta de enlace de back-end, o la cantidad de interfaces es pequeña, el costo adicional de introducir la capa anticorrosión superará los beneficios.

RxJS proporciona más capacidades observables en el escenario de construcción de capas anticorrosión. Si los lectores no necesitan herramientas complejas de conversión de operadores, también pueden crear soluciones de construcción observables por sí mismos. De hecho, solo se pueden usar 100 líneas de código para implementar  mini-rxjs. - Stack Blitz .

La arquitectura de interfaz de usuario transformada ya no dependerá directamente de la implementación de la interfaz y no invadirá el diseño de la capa de datos de interfaz de usuario existente. También puede llevar a cabo el mapeo de conceptos, la adaptación de formato, el almacenamiento en caché de la interfaz, el análisis de estabilidad y ayudar en la depuración conjunta. y pruebas Todo el código de muestra de este artículo está disponible en el repositorio  GitHub - vthinkxie/rxjs-acl: Anti Corruption Layer with RxJS  .

Enlace original

Este artículo es contenido original de Alibaba Cloud y no se puede reproducir sin permiso. 

Supongo que te gusta

Origin blog.csdn.net/yunqiinsight/article/details/123686863
Recomendado
Clasificación