¿El diseño controlado por dominio es un patrón anti-SQL?


44

Me estoy sumergiendo en el diseño impulsado por dominio (DDD) y, aunque profundizo en él, hay algunas cosas que no entiendo. Según tengo entendido, un punto principal es dividir la lógica de dominio (lógica de negocios) de la infraestructura (DB, sistema de archivos, etc.).

Lo que me pregunto es, ¿qué sucede cuando tengo consultas muy complejas, como una consulta de cálculo de recursos materiales? En ese tipo de consulta trabajas con operaciones de conjuntos pesados, el tipo de cosas para las que SQL fue diseñado. Hacer esos cálculos dentro de la capa de dominio y trabajar con muchos conjuntos es como descartar la tecnología SQL.

Hacer estos cálculos en la infraestructura no puede suceder también, porque el patrón DDD permite cambios en la infraestructura sin cambiar la capa de dominio y sabiendo que MongoDB no tiene las mismas capacidades de, por ejemplo, SQL Server, eso no puede suceder.

¿Es eso una trampa del patrón DDD?


34
Si bien SQL está diseñado para manejar álgebra de conjuntos relacionales, no es un día divertido cuando te das cuenta de que la mitad de tu lógica de negocios está enterrada en un puñado de funciones SQL que son difíciles de refactorizar y aún más difíciles de probar. Entonces, mover esto a la capa de dominio donde puede jugar con sus amigos me parece atractivo. ¿Está arrojando una buena parte de la tecnología SQL? Claro, pero SQL es mucho más fácil de administrar cuando solo usa SELECT / JOIN.
Jared Goguen

30
@JaredGoguen, pero esto puede ser porque no eres un experto en SQL y no por la tecnología
Leonardo Mangano

2
@JimmyJames, lo que intenté decir es que si el DDD está bien implementado, permite cambiar las capas con el mínimo esfuerzo, como cambiar de SQL Server a MongoDB. Pero, si tengo consultas complejas en el SQL, es posible que no pueda cambiar a MongoDB debido a sus diferencias técnicas. Creo que dije algo obvio.
Leonardo Mangano

77
... is like throwing away the SQL technologyEl hecho de que una tecnología en particular pueda hacer algo no significa que sea la mejor opción. Es una evidencia anecdótica, pero he conocido demasiadas empresas que solían almacenar la lógica empresarial en la base de datos y están migrando lejos de ella debido a los dolores de cabeza de mantenimiento a largo plazo que causa. Simplificando demasiado, pero las bases de datos están destinadas a almacenar datos y los lenguajes de programación están destinados a transformar datos. No me gustaría usar una base de datos para la lógica de negocios más de lo que me gustaría tratar de usar mi aplicación para almacenar mis datos directamente.
Conor Mancone

8
SQL en sí es un gran ejemplo de DDD. Cuando se enfrentaron con la organización de datos relacionados, las personas primero especificaron un lenguaje para hacerlo: SQL. La implementación realmente no importa mucho. Un administrador de DB no necesita conocer C / C ++ para consultar la base de datos. De manera similar, cuando se enfrenta con la tarea de programar eventos, a alguien se le ocurrió la sintaxis CRON (mhdmw), un modelo de dominio simple que se ajusta al 99% de los problemas de programación. El núcleo de DDD no es crear clases o tablas, etc. Es comprender su problema y crear un sistema para trabajar en su dominio del problema
slebetman

Respuestas:


39

En estos días, es probable que vea lecturas (consultas) manejadas de manera diferente que escrituras (comandos). En un sistema con una consulta complicada, es poco probable que la consulta pase por el modelo de dominio (que es el principal responsable de mantener la consistencia de las escrituras ).

Tiene toda la razón en que deberíamos representar a SQL lo que es SQL. Por lo tanto, diseñaremos un modelo de datos optimizado en torno a las lecturas, y una consulta de ese modelo de datos generalmente tomará una ruta de código que no incluye el modelo de dominio (con la posible excepción de alguna validación de entrada, asegurando que los parámetros en la consulta son razonables)


13
+1 Buena respuesta, pero debe darle a este concepto su nombre propio, Segregación de consulta de comando.
Mike apoya a Mónica el

66
@Mike Tener modelos completamente diferentes para leer y escribir se parece más a CQRS que a CQS.
Andy

3
El "modelo de lectura" no es el modelo de dominio (o parte de él)? No soy un experto en CQRS, pero siempre pensé que el modelo de comando es bastante diferente del modelo de dominio clásico, pero no el modelo de lectura. ¿Entonces quizás puedas dar un ejemplo para esto?
Doc Brown

Me tomó demasiado tiempo darme cuenta de que High Performance Mark estaba llamando la atención sobre un error tipográfico.
VoiceOfUnreason

@DocBrown - aquí está mi intento de aclarar para usted -> cascadefaliure.vocumsineratio.com/2019/04/…
VoiceOfUnreason

21

Según tengo entendido, un punto principal es dividir la lógica de dominio (lógica de negocios) de la infraestructura (DB, sistema de archivos, etc.).

Esta es la base del malentendido: el propósito de DDD no es separar las cosas a lo largo de una línea dura como "esto está en el servidor SQL, por lo que no debe ser BL", el propósito de DDD es separar dominios y crear barreras entre aquellos que permiten que los elementos internos de un dominio estén completamente separados de los elementos internos de otro dominio, y que definan elementos externos compartidos entre ellos.

No piense en "estar en SQL" como la barrera BL / DL, eso no es lo que es. En cambio, piense en "este es el fin del dominio interno" como la barrera.

Cada dominio debe tener API externas que le permitan trabajar con todos los demás dominios: en el caso de la capa de almacenamiento de datos , debe tener acciones de lectura / escritura (CRUD) para los objetos de datos que almacena. Esto significa que SQL en sí no es realmente la barrera, lo son los componentes VIEWy PROCEDURE. Nunca debe leer directamente de la tabla: ese es el detalle de implementación que DDD nos dice que, como consumidor externo, no deberíamos preocuparnos.

Considera tu ejemplo:

Lo que me pregunto es, ¿qué sucede cuando tengo consultas muy complejas, como una consulta de cálculo de recursos materiales? En ese tipo de consulta trabajas con operaciones de conjuntos pesados, el tipo de cosas para las que SQL fue diseñado.

Esto es exactamente lo que debería estar en SQL entonces, y no es una violación de DDD. Es para lo que hicimos DDD . Con ese cálculo en SQL, eso se convierte en parte de BL / DL. Lo que debería hacer es usar una vista separada / procedimiento almacenado / lo que tenga, y mantener la lógica de negocios separada de la capa de datos, ya que esa es su API externa. De hecho, su capa de datos debe ser otra capa de dominio DDD, donde su capa de datos tiene sus propias abstracciones para trabajar con las otras capas de dominio.

Hacer estos cálculos en la infraestructura no puede suceder también, porque el patrón DDD permite cambios en la infraestructura sin cambiar la capa de dominio y sabiendo que MongoDB no tiene las mismas capacidades de, por ejemplo, SQL Server, eso no puede suceder.

Ese es otro malentendido: dice que los detalles de implementación internamente pueden cambiar sin cambiar otras capas de dominio. No dice que puede reemplazar una pieza de infraestructura completa.

Nuevamente, tenga en cuenta que DDD se trata de ocultar elementos internos con API externas bien definidas. La ubicación de esas API es una pregunta totalmente diferente, y DDD no define eso. Simplemente define que estas API existen y que nunca deberían cambiar .

DDD no está configurado para permitirle reemplazar ad-hoc MSSQL con MongoDB; esos son dos componentes de infraestructura totalmente diferentes.

En cambio, usemos una analogía para lo que DDD define: autos de gasolina versus eléctricos. Ambos vehículos tienen dos métodos completamente diferentes para crear propulsión, pero tienen los mismos API: un encendido / apagado, un acelerador / freno y ruedas para impulsar el vehículo. DDD dice que deberíamos poder reemplazar el motor (gas o eléctrico) en nuestro automóvil. No dice que podamos reemplazar el automóvil con una motocicleta, y eso es efectivamente lo que es MSSQL → MongoDB.


1
Gracias por la explicación. Para mí es un tema muy difícil, todos tienen un punto de vista diferente. Lo único que no estoy de acuerdo es la comparación entre MSSQL (automóvil) y MongoDB (motocicleta), para mí la comparación correcta es que estos son dos motores diferentes para el mismo automóvil, pero es solo una opinión.
Leonardo Mangano

8
@LeonardoMangano Ah, pero no lo son. MSSQL es una base de datos relacional, MongoDB es una base de datos de documentos. Sí, "base de datos" describe ambos, pero eso es lo más lejos posible. Las técnicas de lectura / escritura son completamente diferentes. En lugar de MongoDB, podría usar Postgre o MySQL como alternativa, y eso sería una comparación válida.
410_ Se fue el

3
"Nunca deberías leer directamente de la mesa ..." Locura.
jpmc26

"Nunca debería leer directamente de la tabla ..." Esta es una regla que he venido a implementar por mi cuenta después de una década de escribir software que interactúa con bases de datos y sufre el dolor temprano de tratar de seguir tutoriales estructurados en torno a Patrones de diseño populares.
Lucifer Sam

@LuciferSam Aye. Hace que sea mucho más fácil administrar la separación entre los detalles de implementación y los límites del dominio. Un "objeto" en el dominio podría estar representado por 5 tablas, por lo que usamos una Vista para encapsular ese objeto.
410_ Se fue

18

Si alguna vez ha estado en un proyecto donde la organización que paga para alojar la aplicación decide que las licencias de la capa de base de datos son demasiado caras, apreciará la facilidad con la que puede migrar su base de datos / almacenamiento de datos. A fin de cuentas, si bien esto sucede, no sucede a menudo .

Puedes obtener lo mejor de ambos mundos, por así decirlo. Si considera realizar las funciones complejas de la base de datos como una optimización, puede usar una interfaz para inyectar una implementación alternativa del cálculo. El problema es que debe mantener la lógica en varias ubicaciones.

Desviarse de un patrón arquitectónico

Cuando se encuentre en desacuerdo con la implementación de un patrón puramente, o con una desviación en alguna área, entonces tiene que tomar una decisión. Un patrón es simplemente una forma de hacer cosas para ayudar a organizar su proyecto. En este punto, tome tiempo para evaluar:

  • ¿Es este el patrón correcto? (muchas veces lo es, pero a veces es un mal ajuste)
  • ¿Debo desviarme de esta manera?
  • ¿Hasta dónde me he desviado hasta ahora?

Encontrará que algunos patrones arquitectónicos se ajustan bien al 80-90% de su aplicación, pero no tanto para los bits restantes. La desviación ocasional del patrón prescrito es útil por razones de rendimiento o logísticas.

Sin embargo, si encuentra que sus desviaciones acumulativas representan más del 20% de la arquitectura de su aplicación, probablemente sea un mal ajuste.

Si elige continuar con la arquitectura, hágase un favor y documente dónde y por qué se desvió de la forma prescrita de hacer las cosas. Cuando obtenga un nuevo miembro entusiasta en su equipo, puede indicarle esa documentación que incluye las mediciones de rendimiento y las justificaciones. Eso reducirá la probabilidad de que se repitan solicitudes para solucionar el "problema". Esa documentación también ayudará a desincentivar las desviaciones desenfrenadas.


Evitaría el uso de frases como "este es el patrón correcto" en las respuestas. Es bastante difícil lograr que las personas sean específicas cuando escriben sus preguntas, y por su propia admisión "a veces es un mal ajuste", lo que sugiere que no, no es el patrón correcto.
Robert Harvey

@RobertHarvey, he estado en proyectos donde el patrón utilizado simplemente no era el adecuado para la aplicación, lo que provocó que fallara ciertas métricas de calidad. Ciertamente no es la norma, pero cuando eso sucede, usted tiene la difícil decisión de cambiar las arquitecturas o mantener el código de horquillado en la aplicación. Cuanto antes pueda determinar el mal ajuste, más fácil será arreglarlo. Es por eso que siempre incluyo ese pensamiento al evaluar casos extremos. Junto con la última viñeta, a veces no te das cuenta de lo mal que está hasta que ves la acumulación de desviaciones.
Berin Loritsch

7

La lógica de manipulación de conjuntos en la que SQL es bueno puede integrarse con DDD sin problemas.

Digamos, por ejemplo, que necesito saber algún valor agregado, recuento total de productos por tipo. Fácil de ejecutar en sql, pero lento si cargo todos los productos en la memoria y los agrego a todos.

Simplemente presento un nuevo objeto de dominio,

ProductInventory
{
    ProductType
    TotalCount
    DateTimeTaken
}

y un método en mi repositorio

ProductRepository
{
    List<ProductInventory> TakeInventory(DateTime asOfDate) {...}
}

Claro, tal vez ahora estoy confiando en que mi DB tenga ciertas habilidades. Pero técnicamente todavía tengo la separación y mientras la lógica sea simple, puedo argumentar que no es 'lógica de negocios'


Bueno, hasta ahora lo recuerdo. Se supone que los repositorios también se obtienen Querycomo parámetros. repository.find(query);. He leído lo mismo pero con Specs. That opens a door to leave Query` como abstracción y / QueryImplo la implementación de consultas específicas para la capa de infraestructura.
Laiv

55
oh dios, sé que algunas personas hacen eso, pero creo que es horrible. Puede ver este tipo de cosas como un paso por ese camino. Pero creo que se puede tomar con precaución.
Ewan

I know some people do thatAlgunas personas son fundamentales y su marco. SpringFramework tiene mucho de esto :-). De todos modos, como ha sugerido @VoiceOfUnreason, la clave en torno a DDD es mantener la consistencia de los escritos. No estoy seguro de forzar el diseño con modelos de dominio cuyo único propósito es consultar o parametrizar consultas. Eso podría abordarse fuera del dominio con estructuras de datos (pocos, pojos, dtos, mapeadores de filas, lo que sea).
Laiv

obviamente necesitamos algún tipo de inquisición para ayudar a esas personas a recuperar la cordura. Pero me estoy quedando con mis armas. La exposición parcial de la capa de datos es aceptable cuando objetivamente hace una mejor aplicación, donde lo que es o no un "Objeto de dominio" es subjetivo
Ewan

1
@LeonardoMangano depende de su aplicación e implementación. Lo principal a tener en cuenta es que puede reinterpretar su dominio para que sea factible.
Ewan

3

Una de las formas posibles de resolver este dilema es pensar en SQL como un lenguaje ensamblador: rara vez, si es que lo hace, codifica directamente en él, pero cuando el rendimiento es importante, debe ser capaz de comprender el código producido por su C / C ++ / Golang / Rust compilador y tal vez incluso escribir un pequeño fragmento en el ensamblaje, si no puede cambiar el código en su lenguaje de alto nivel para producir el código de máquina deseado.

Del mismo modo, en el ámbito de las bases de datos y SQL, varias bibliotecas de SQL (algunas de las cuales son ORM ), por ejemplo, SQLAlchemy y Django ORM para Python, LINQ para .NET, proporcionan abstracciones de nivel superior pero utilizan código SQL generado cuando sea posible para lograr el rendimiento. También proporcionan cierta portabilidad en cuanto a la base de datos utilizada, posiblemente con un rendimiento diferente, por ejemplo, en Postgres y MySQL, debido a algunas operaciones que utilizan un SQL específico de base de datos más óptimo.

Y al igual que con los lenguajes de alto nivel, es fundamental comprender cómo funciona SQL, incluso si solo se trata de reorganizar las consultas realizadas con las bibliotecas SQL mencionadas anteriormente, para poder lograr la eficiencia deseada.

PD: Prefiero hacer de esto un comentario, pero no tengo suficiente reputación para eso.


2

Como de costumbre, esta es una de esas cosas que depende de varios factores. Es cierto que hay mucho que puedes hacer con SQL. También existen desafíos con su uso y algunas limitaciones prácticas de las bases de datos relacionales.

Como Jared Goguen señala en los comentarios, SQL puede ser muy difícil de probar y verificar. Los principales factores que conducen a esto son que no puede (en general) descomponerse en componentes. En la práctica, una consulta compleja debe considerarse in toto. Otro factor complicado es que el comportamiento y la corrección de SQL dependen en gran medida de la estructura y el contenido de sus datos. Esto significa que probar todos los escenarios posibles (o incluso determinar cuáles son) a menudo es inviable o imposible. La refactorización de SQL y la modificación de la estructura de la base de datos también es problemática.

El otro gran factor que ha llevado a alejarse de SQL es que las bases de datos relacionales tienden a escalar solo verticalmente. Por ejemplo, cuando crea cálculos complejos en SQL para ejecutarlos en SQL Server, se ejecutarán en la base de datos. Eso significa que todo ese trabajo está utilizando recursos en la base de datos. Cuanto más haga en SQL, más recursos necesitará su base de datos tanto en términos de memoria como de CPU. A menudo es menos eficiente hacer estas cosas en otros sistemas, pero no hay un límite práctico para la cantidad de máquinas adicionales que puede agregar a dicha solución. Este enfoque es menos costoso y más tolerante a fallas que construir un servidor de base de datos monstruoso.

Estos problemas pueden o no aplicarse al problema en cuestión. Si puede resolver su problema con los recursos de base de datos disponibles, tal vez SQL esté bien para su espacio de problemas. Sin embargo, debe considerar el crecimiento. Puede que esté bien hoy, pero dentro de unos años, el costo de agregar recursos adicionales puede convertirse en un problema.


¿No es la alternativa a una base de datos de monstruos, simplemente un número monstruoso y una diversidad de sistemas auxiliares? ¿Qué capacidad de recuperación tienen los sistemas auxiliares, si todos cuelgan del sistema central? Y si la justificación es simplemente la limitación tecnológica del sistema central, esto a menudo será una optimización prematura para la mayoría de los sistemas comerciales. En general, SQL puede escribirse de manera desacoplada, si se considera necesario.
Steve

@ Steve Creo que donde te equivocaste aquí está asumiendo que debe haber un único sistema central del que otros se "cuelguen".
JimmyJames

@Steve Para dar un ejemplo, puede reemplazar una base de datos completa del sistema con una sola base de datos sin SQL (no digo que esta sea siempre la opción correcta, solo que se puede hacer). Esa base de datos se puede almacenar en muchos sistemas, incluso regiones geográficas. Tal base de datos no es auxiliar, es un reemplazo total de la base de datos SQL.
JimmyJames

@JimmyJames, de acuerdo, pero cuando no hay un sistema central, eso puede crear sus propios problemas al analizar las dependencias y mantener la consistencia de los datos. Esa es la razón de los monolitos en primer lugar: crean un cierto tipo de simplicidad y, por lo tanto, ciertos tipos de análisis y eficiencias de mantenimiento. Las soluciones no monolíticas simplemente intercambian algunos problemas o costos por otros.
Steve

@jmoreno Lanzar recursos en algo para cojear no es lo que yo llamaría una buena ingeniería: "para manejar el volumen de datos masivo del sitio, y está ejecutando 9,000 instancias de memcached para mantener el número de transacciones la base de datos debe servir ". ¿Considera el costo de sus diseños o supone que alguien pagará dinero para que sus preferencias personales sean viables?
JimmyJames

2

¿Es eso una trampa del patrón DDD?

Permítanme primero aclarar algunos conceptos erróneos.

DDD no es un patrón. Y en realidad no prescribe patrones.

El prefacio del libro DDD de Eric Evan dice:

Los principales diseñadores de software han reconocido el diseño y el modelado de dominios como temas críticos durante al menos 20 años, pero sorprendentemente se ha escrito poco sobre lo que hay que hacer o cómo hacerlo. Aunque nunca se ha formulado claramente, una filosofía ha surgido como una corriente subterránea en la comunidad de objetos, una filosofía que llamo diseño impulsado por dominios.

[...]

Una característica común a los éxitos fue un modelo de dominio rico que evolucionó a través de iteraciones de diseño y se convirtió en parte del tejido del proyecto.

Este libro proporciona un marco para tomar decisiones de diseño y un vocabulario técnico para discutir el diseño de dominios. Es una síntesis de las mejores prácticas ampliamente aceptadas junto con mis propias ideas y experiencias.

Por lo tanto, es una forma de abordar el desarrollo de software y el modelado de dominios, además de un vocabulario técnico que respalda esas actividades (un vocabulario que incluye varios conceptos y patrones). Tampoco es algo completamente nuevo.

Otra cosa a tener en cuenta es que un modelo de dominio no es la implementación OO del mismo que se puede encontrar en su sistema, esa es solo una forma de expresarlo o expresar alguna parte de él. Un modelo de dominio es la forma en que piensa sobre el problema que está tratando de resolver con el software. Es cómo entiendes y percibes las cosas, cómo hablas de ellas. Es conceptual . Pero no en un sentido vago. Es profundo y refinado, y es el resultado del trabajo duro y la recopilación de conocimientos. Se refina aún más y probablemente evolucionó con el tiempo, e implica consideraciones de implementación (algunas de las cuales pueden restringir el modelo). Debe ser compartido por todos los miembros del equipo. (y expertos en dominios involucrados), y debería conducir la forma en que implementa el sistema, para que el sistema lo refleje de cerca.

Nada de eso es inherentemente pro o anti-SQL, aunque los desarrolladores de OO son quizás generalmente mejores para expresar el modelo en lenguajes de OO, y la expresión de muchos conceptos de dominio está mejor respaldada por OOP. Pero a veces partes del modelo deben expresarse en un paradigma diferente.

Lo que me pregunto es, ¿qué sucede cuando tengo consultas muy complejas [...]?

Bueno, en general, hay dos escenarios aquí.

En el primer caso, algún aspecto de un dominio realmente requiere una consulta compleja, y tal vez ese aspecto se exprese mejor en el paradigma SQL / relacional, así que use la herramienta adecuada para el trabajo. Refleje esos aspectos en su pensamiento de dominio y el lenguaje utilizado en la comunicación de conceptos. Si el dominio es complejo, tal vez sea parte de un subdominio con su propio contexto acotado.

El otro escenario es que la necesidad percibida de expresar algo en SQL es el resultado de un pensamiento restringido. Si una persona o un equipo siempre han estado orientados a la base de datos en su pensamiento, puede ser difícil para ellos, solo debido a la inercia, ver una forma diferente de abordar las cosas. Esto se convierte en un problema cuando la vieja forma no satisface las nuevas necesidades y requiere un poco de pensamiento fuera de la caja. DDD, como un enfoque para el diseño, se trata en parte de formas de salir de esa caja reuniendo y destilando el conocimiento sobre el dominio. Pero todo el mundo parece ignorar esa parte del libro, y se enfoca en algunos de los vocabulario y patrones técnicos enumerados.


0

La secuela se hizo popular cuando la memoria era costosa, porque el modelo de datos relacionales brindaba la posibilidad de normalizar sus datos y almacenarlos efectivamente en el sistema de archivos.

Ahora la memoria es relativamente barata, por lo que podemos omitir la normalización y almacenarla en el formato que la usamos o incluso duplicar una gran cantidad de datos por razones de velocidad.

Considere la base de datos como un simple dispositivo IO , cuya responsabilidad es almacenar datos en el sistema de archivos; sí, sé que es difícil imaginarlo, porque escribimos muchas aplicaciones con lógica comercial importante escrita en consultas SQL, pero solo trate de imaginar ese SQL Server Es solo otra impresora.

¿Incrustaría el generador de PDF en el controlador de la impresora o agregaría un activador que imprima la página de registro para cada pedido de venta impreso desde nuestra impresora?

Supongo que la respuesta será no, porque no queremos que nuestra aplicación esté acoplada al tipo de dispositivo específico (ni siquiera hablando de la eficiencia de tal idea)

En los años 70-90, la base de datos SQL era eficiente, ¿ahora? - No estoy seguro, en algunos escenarios, la consulta asíncrona de datos devolverá los datos requeridos más rápido que las combinaciones múltiples en la consulta SQL.

SQL no fue diseñado para consultas complicadas, fue diseñado para almacenar datos de manera eficiente y luego proporcionar interfaz / lenguaje para consultar datos almacenados.

Yo diría que construir su aplicación alrededor del modelo de datos relacionales con consultas complicadas es abuso del motor de base de datos. Por supuesto, los proveedores de motores de bases de datos están contentos cuando acoplas estrechamente tu negocio a su producto; estarán más que felices de proporcionar más funciones que fortalezcan este límite.


1
Pero sigo pensando que SQL es mucho mejor para los cálculos establecidos que cualquier otro lenguaje. Desde mi punto de vista. su ejemplo está al revés, usando C # para operaciones de conjuntos muy complejas con millones de filas y uniones involucradas está usando la herramienta incorrecta, pero podría estar equivocado.
Leonardo Mangano

@LeonardoMangano, algunos ejemplos: con c # puedo agrupar millones de filas y calcularlas en paralelo, puedo recuperar datos de forma asincrónica y ejecutar cálculos "a tiempo" cuando se devuelven datos, con c # puedo hacer cálculos con poco uso de memoria enumerando filas por fila Tener una lógica compleja en el código le proporcionará muchas opciones sobre cómo hacer cálculos.
Fabio
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.