¿Cómo se organiza un software altamente personalizado?

28

Estoy trabajando en un gran proyecto de software altamente personalizado para varios clientes de todo el mundo. Esto significa que tenemos quizás un 80% de código, que es común entre los distintos clientes, pero también mucho código que tiene que cambiar de un cliente a otro. En el pasado hicimos nuestro desarrollo en repositorios separados (SVN) y cuando comenzó un nuevo proyecto (tenemos pocos, pero grandes clientes) creamos otro repositorio basado en cualquier proyecto pasado que tenga la mejor base de código para nuestras necesidades. Esto ha funcionado en el pasado, pero nos encontramos con varios problemas:

  • Los errores que se corrigen en un repositorio no se reparan en otros repositorios. Esto podría ser un problema de organización, pero me resulta difícil corregir y parchear un error en 5 repositorios diferentes, teniendo en cuenta que el equipo que mantiene este repositorio podría estar en otra parte del mundo y no tenemos su entorno de prueba , ni conoce su programación ni los requisitos que tienen (un "error" en un país podría ser una "característica" en otro).
  • Las características y mejoras realizadas para un proyecto, que también podrían ser útiles para otro proyecto, se pierden o si se usan en otro proyecto a menudo causan grandes dolores de cabeza al fusionarlas de una base de código a otra (ya que ambas ramas podrían haberse desarrollado de forma independiente durante un año) )
  • Las refactorizaciones y las mejoras de código realizadas en una rama de desarrollo se pierden o causan más daño que bien si tiene que fusionar todos estos cambios entre las ramas.

Ahora estamos discutiendo cómo resolver estos problemas y hasta ahora se nos ocurrieron las siguientes ideas sobre cómo resolver esto:

  1. Mantenga el desarrollo en ramas separadas pero organícelo mejor al tener un repositorio central donde se fusionen las correcciones de errores generales y hacer que todos los proyectos combinen los cambios de este repositorio central en los suyos de forma regular (por ejemplo, diariamente). Esto requiere una gran disciplina y mucho esfuerzo para fusionarse entre las ramas. Por lo tanto, no estoy convencido de que funcione y podemos mantener esta disciplina, especialmente cuando la presión del tiempo aumenta.

  2. Abandone las ramas de desarrollo separadas y tenga un repositorio de código central donde viva todo nuestro código y realice nuestra personalización al tener módulos conectables y opciones de configuración. Ya estamos utilizando contenedores de Inyección de dependencias para resolver dependencias en nuestro código y estamos siguiendo el patrón MVVM en la mayoría de nuestro código para separar limpiamente la lógica de negocios de nuestra interfaz de usuario.

El segundo enfoque parece ser más elegante, pero tenemos muchos problemas sin resolver en este enfoque. Por ejemplo: cómo maneja los cambios / adiciones en su modelo / base de datos. Estamos usando .NET con Entity Framework para tener entidades fuertemente tipadas. No veo cómo podemos manejar las propiedades que se requieren para un cliente pero que son inútiles para otro cliente sin saturar nuestro modelo de datos. Estamos pensando en resolver esto en la base de datos utilizando tablas satelitales (que tienen tablas separadas donde nuestras columnas adicionales para una entidad específica viven con un mapeo 1: 1 a la entidad original), pero esta es solo la base de datos. ¿Cómo manejas esto en código? Nuestro modelo de datos vive en una biblioteca central que no podríamos ampliar para cada cliente utilizando este enfoque.

Estoy seguro de que no somos el único equipo que lucha con este problema y me sorprende encontrar tan poco material sobre el tema.

Entonces mis preguntas son las siguientes:

  1. ¿Qué experiencia tiene con un software altamente personalizado, qué enfoque eligió y cómo funcionó para usted?
  2. ¿Qué enfoque me recomiendan y por qué? ¿Hay un mejor enfoque?
  3. ¿Hay algún buen libro o artículo sobre el tema que pueda recomendar?
  4. ¿Tiene recomendaciones específicas para nuestro entorno técnico (.NET, Entity Framework, WPF, DI)?

Editar:

Gracias por todas las sugerencias. La mayoría de las ideas coinciden con las que ya teníamos en nuestro equipo, pero es realmente útil ver la experiencia que tuvo con ellos y consejos para implementarlos mejor.

Todavía no estoy seguro de qué camino tomaremos y no estoy tomando la decisión (solo), pero transmitiré esto en mi equipo y estoy seguro de que será útil.

Por el momento, el tenor parece ser un repositorio único que utiliza varios módulos específicos del cliente. No estoy seguro de que nuestra arquitectura esté a la altura o cuánto tengamos que invertir para que encaje, por lo que algunas cosas podrían vivir en repositorios separados por un tiempo, pero creo que es la única solución a largo plazo que funcionará.

Entonces, gracias de nuevo por todas las respuestas!

aKzenT
fuente
Considere tratar las tablas de la base de datos como código.
Ya estamos haciendo esto en el sentido de que tenemos nuestros scripts de base de datos en nuestro repositorio de subversión, pero en realidad no resuelve los problemas mencionados anteriormente. No queremos tener tablas de estilo Key-Value en nuestro modelo de base de datos porque vienen con muchos problemas. Entonces, ¿cómo permite adiciones a su modelo para clientes individuales mientras mantiene un repositorio de código compartido para todos ellos?
aKzenT

Respuestas:

10

Parece que el problema fundamental no es solo el mantenimiento del repositorio de código, sino la falta de una arquitectura adecuada .

  1. ¿Cuál es el núcleo / esencia del sistema, que siempre será compartido por todos los sistemas?
  2. ¿Qué mejoras / desviaciones requiere cada cliente?

Un marco o biblioteca estándar abarca el primero, mientras que el segundo se implementaría como complementos (complementos, subclases, DI, lo que tenga sentido para la estructura del código).

Un sistema de control de fuente que maneje sucursales y desarrollo distribuido probablemente también ayudaría; Soy fanático de Mercurial, otros prefieren Git. El marco sería la rama principal, cada sistema personalizado sería sub-ramas, por ejemplo.

Las tecnologías específicas utilizadas para implementar el sistema (.NET, WPF, lo que sea) no son importantes en gran medida.

Hacer esto bien no es fácil , pero es crítico para la viabilidad a largo plazo. Y, por supuesto, cuanto más espere, mayor será la deuda técnica con la que tendrá que lidiar.

Puede encontrar útil el libro Arquitectura de software: principios y patrones organizacionales .

¡Buena suerte!

Steven A. Lowe
fuente
3
Sip. Sin embargo, la arquitectura suele ser más psicológica que técnica. Si te enfocas completamente en el producto, terminarás con un desglose del trabajo donde los componentes solo son útiles para ese producto. Si, por otro lado, te enfocas en crear un conjunto de bibliotecas que serán más útiles en general, entonces creas un conjunto de capacidades que se pueden implementar en una gama más amplia de situaciones. La clave, por supuesto, es encontrar el equilibrio adecuado entre estos dos extremos para su situación particular.
William Payne
Creo que hay una gran parte compartida entre muchas de nuestras sucursales (> 90%), pero el último 10% siempre se encuentra en diferentes lugares, por lo que hay muy pocos componentes donde no puedo imaginar algunos cambios específicos del cliente, excepto algunas bibliotecas de servicios públicos que no contienen ninguna lógica de negocios.
aKzenT
@aKzenT: hmmm ... no te imagines, mide en su lugar. Examine el código y observe todos los lugares donde se ha producido la personalización, haga una lista de los componentes modificados, observe con qué frecuencia se ha modificado cada componente y piense en los tipos y patrones de modificaciones que realmente se han realizado. ¿Son cosméticos o algorítmicos? ¿Están agregando o cambiando la funcionalidad base? ¿Cuál es la razón de cada tipo de cambio? Nuevamente, este es un trabajo duro , y es posible que no le gusten las implicaciones de lo que descubre.
Steven A. Lowe
1
Acepté esto como respuesta, porque no podemos tomar esta decisión antes de pensar más en nuestra arquitectura, identificar partes que son comunes o que DEBERÍAN ser comunes y luego ver si podemos vivir con un solo repositorio o si es necesario bifurcar (para el momento al menos). Esta respuesta refleja esta mejor OMI. ¡Gracias por todas las otras publicaciones, sin embargo!
aKzenT
11

Una empresa para la que he trabajado tenía el mismo problema, y ​​el enfoque para abordar el problema fue este: se creó un marco común para todos los proyectos nuevos; Esto incluye todo lo que tiene que ser igual en cada proyecto. Por ejemplo, herramientas de generación de formularios, exportación a Excel, registro. Se hizo un esfuerzo para asegurarse de que este marco común solo se mejora (cuando un nuevo proyecto necesita nuevas características), pero nunca se bifurca.

Basado en ese marco, el código específico del cliente se mantuvo en repositorios separados. Cuando es útil o necesario, las correcciones de errores y las mejoras se copian entre proyectos (con todas las advertencias descritas en la pregunta). Sin embargo, las mejoras globalmente útiles entran en el marco común.

Tener todo en una base de código común para todos los clientes tiene algunas ventajas, pero por otro lado, leer el código se vuelve difícil cuando hay innumerables ifmensajes para hacer que el programa se comporte de manera diferente para cada cliente.

EDITAR: Una anécdota para hacer esto más comprensible:

El dominio de esa empresa es la gestión de almacenes, y una tarea de un sistema de gestión de almacenes es encontrar una ubicación de almacenamiento gratuita para los productos entrantes. Suena fácil, pero en la práctica, se deben observar muchas restricciones y estrategias.

En un momento dado, la gerencia le pidió a un programador que creara un módulo flexible y parametrizable para encontrar ubicaciones de almacenamiento, que implementó varias estrategias diferentes y debería haberse utilizado en todos los proyectos posteriores. El noble esfuerzo resultó en un módulo complejo, que fue muy difícil de entender y mantener. En el siguiente proyecto, el líder del proyecto no pudo encontrar la manera de hacer que funcione en ese almacén, y el desarrollador de dicho módulo desapareció, por lo que finalmente lo ignoró y escribió un algoritmo personalizado para esa tarea.

Unos años más tarde, el diseño del almacén donde se utilizó originalmente este módulo cambió y el módulo con toda su flexibilidad no cumplió con los nuevos requisitos; así que también lo reemplacé con un algoritmo personalizado.

Sé que LOC no es una buena medida, pero de todos modos: el tamaño del módulo "flexible" era ~ 3000 LOC (PL / SQL), mientras que un módulo personalizado para la misma tarea toma ~ 100..250 LOC. Por lo tanto, tratar de ser flexible aumentó enormemente el tamaño de la base del código, sin obtener la reutilización que esperábamos.

usuario281377
fuente
Gracias por sus comentarios. Esta es una extensión de la primera solución descrita y también pensamos en esto. Sin embargo, dado que la mayoría de nuestras bibliotecas usan algunas entidades centrales y estas entidades centrales generalmente se extienden para un cliente u otro, creo que solo podríamos poner muy pocas bibliotecas en este repositorio central. Por ejemplo, tenemos una entidad "Cliente" definida y ORM mapeado en una biblioteca que es utilizada por casi todas nuestras otras bibliotecas y programas. Pero cada cliente tiene algunos campos adicionales que deben agregar a un "Cliente", por lo que necesitaríamos bifurcar esta biblioteca y, por lo tanto, todas las bibliotecas dependiendo de ella.
aKzenT
3
Nuestra idea de evitar innumerables "ifs" es hacer un uso extensivo de la inyección de dependencias e intercambiar módulos completos para diferentes clientes. Sin embargo, no estoy seguro de cómo funcionará esto. ¿Cómo te funcionó este enfoque?
aKzenT
+1, esto básicamente coincide con mi experiencia en proyectos que lo han manejado bien. Si va a usar el enfoque 'ifs para diferentes clientes', 2 puntos: 1. No haga if (customer1) ..., sino if (configurationOption1) ... y tenga opciones de configuración por cliente. 2. ¡Intenta no hacerlo! Tal vez el 1% del tiempo sea la mejor opción (más comprensible / fácil de mantener) que tener módulos específicos de configuración.
vaughandroid
@Baqueta: Solo para aclarar: recomienda usar módulos por cliente en lugar de opciones de configuración (ifs), ¿verdad? Me gusta su idea de diferenciar entre características en lugar de clientes. Entonces, la combinación de ambos sería tener varios "módulos de características" que están controlados por las opciones de configuración. Entonces, un cliente solo se representa como un conjunto de módulos de características independientes. Me gusta mucho este enfoque, pero no estoy seguro de cómo diseñarlo. DI resuelve el problema de la carga e intercambio de módulos, pero ¿cómo gestiona los diferentes modelos de datos entre clientes?
aKzenT
Sí, módulos por función y luego configuración / selección de características por cliente. DI sería ideal. Por desgracia, nunca he tenido que combinar sus necesidades de personalización sustancial por cliente con una sola biblioteca de datos, sin embargo, así que no estoy seguro de que puedo ser de mucha ayuda allí ...
vaughandroid
5

Uno de los proyectos en los que he trabajado soportó múltiples plataformas (más de 5) en una gran cantidad de lanzamientos de productos. Muchos de los desafíos que está describiendo fueron cosas que enfrentamos, aunque de una manera ligeramente diferente. Teníamos una base de datos patentada, por lo que no tuvimos los mismos tipos de problemas en ese campo.

Nuestra estructura era similar a la suya, pero teníamos un único repositorio para nuestro código. El código específico de la plataforma entró en sus propias carpetas de proyecto dentro del árbol de código. El código común vivía dentro del árbol según la capa a la que pertenecía.

Tuvimos una compilación condicional, basada en la plataforma que se está construyendo. Mantener eso fue una molestia, pero solo tuvo que hacerse cuando se agregaron nuevos módulos en la capa específica de la plataforma.

Tener todo el código en un único repositorio nos facilitó la corrección de errores en múltiples plataformas y lanzamientos al mismo tiempo. Teníamos un entorno de construcción automatizado para que todas las plataformas sirvieran de respaldo en caso de que un nuevo código rompiera una plataforma supuestamente no relacionada.

Tratamos de desalentarlo, pero habría casos en los que una plataforma necesitara una solución basada en un error específico de la plataforma que de otro modo estaría en un código común. Si pudiéramos anular la compilación condicionalmente sin hacer que el módulo se vea fugitivo, lo haríamos primero. Si no, moveríamos el módulo fuera del territorio común y lo empujaríamos a una plataforma específica.

Para la base de datos, teníamos algunas tablas que tenían columnas / modificaciones específicas de la plataforma. Nos aseguraríamos de que cada versión de plataforma de la tabla cumpliera con un nivel básico de funcionalidad para que el código común pudiera hacer referencia a ella sin preocuparse por las dependencias de la plataforma. Las consultas / manipulaciones específicas de la plataforma se introdujeron en las capas del proyecto de la plataforma.

Entonces, para responder a sus preguntas:

  1. Muchos, y ese fue uno de los mejores equipos con los que he trabajado. Codebase en ese momento era de alrededor de 1M loc. No pude elegir el enfoque, pero funcionó bastante bien. Incluso en retrospectiva, no he visto una mejor manera de manejar las cosas.
  2. Recomiendo el segundo enfoque que sugirió con los matices que menciono en mi respuesta.
  3. No hay libros en los que pueda pensar, pero investigaría el desarrollo multiplataforma como iniciador.
  4. Instituir un gobierno fuerte. Es la clave para asegurarse de que se sigan sus estándares de codificación. Seguir esos estándares es la única forma de mantener las cosas manejables y mantenibles. Tuvimos nuestra parte de súplicas apasionadas para romper el modelo que estábamos siguiendo, pero ninguna de esas apelaciones influyó en todo el equipo de desarrollo senior.

fuente
Gracias por compartir tu experiencia. Solo para entender mejor: ¿Cómo se define una plataforma en su caso? Windows, Linux, X86 / x64? ¿O algo más relacionado con diferentes clientes / entornos? Estás haciendo un buen punto en 4) Creo que es uno de los problemas que tenemos. Tenemos un equipo de muchas personas muy inteligentes y calificadas, pero todos tienen ideas ligeramente diferentes sobre cómo hacer las cosas. Sin alguien claramente a cargo de la arquitectura, es difícil acordar una estrategia común y se arriesga a perderse en las discusiones sin cambiar nada.
aKzenT
@aKzenT: sí, me refería al hardware físico y al sistema operativo como plataformas. Teníamos un gran conjunto de características, algunas de las cuales podían seleccionarse mediante un módulo de licencia. Y admitimos una amplia gama de hardware. Relacionado con eso, teníamos un directorio de capa común que tenía un montón de dispositivos con sus propios directorios en esa capa para el árbol de código. Así que nuestras circunstancias realmente no estaban tan lejos de donde estás. Nuestros desarrolladores principales habrían animado debates entre ellos, pero una vez que se tomó una decisión colectiva, todos acordaron seguir la línea.
4

Trabajé durante muchos años en una aplicación de Administración de Pensiones que tenía problemas similares. Los planes de pensiones son muy diferentes entre las compañías y requieren un conocimiento altamente especializado para implementar la lógica de cálculo e informes y también un diseño de datos muy diferente. Solo puedo dar una breve descripción de parte de la arquitectura, pero tal vez dará suficiente idea.

Teníamos 2 equipos separados: un equipo de desarrollo central , que era responsable del código del sistema central (que sería el 80% del código compartido anteriormente) y un equipo de implementación , que tenía experiencia en el dominio de los sistemas de pensiones y era responsable del aprendizaje del cliente. requisitos y scripts de codificación e informes para el cliente.

Teníamos todas nuestras tablas definidas por Xml (esto antes del momento en que los marcos de entidades eran probados y comunes). El equipo de implementación diseñaría todas las tablas en Xml, y se podría solicitar a la aplicación principal que genere todas las tablas en Xml. También hubo archivos de script VB asociados, Crystal Reports, documentos de Word, etc. para cada cliente. (También había un modelo de herencia integrado en Xml para permitir la reutilización de otras implementaciones).

La aplicación principal (una aplicación para todos los clientes) almacenaba en caché todas las cosas específicas del cliente cuando llegaba una solicitud para ese cliente, y generaba un objeto de datos común (algo así como un conjunto de registros ADO remotos), que podía ser serializado y pasado alrededor.

Este modelo de datos es menos hábil que los objetos de entidad / dominio, pero es muy flexible, universal y puede procesarse mediante un conjunto de código central. Quizás en su caso, podría definir sus objetos de entidad base con solo los campos comunes y tener un Diccionario adicional para campos personalizados (agregue algún tipo de conjunto de descriptores de datos a su objeto de entidad para que tenga metadatos para los campos personalizados. )

Teníamos repositorios de origen separados para el código del sistema central y para el código de implementación.

Nuestro sistema central en realidad tenía muy poca lógica comercial, aparte de algunos módulos de cálculo comunes muy estándar. El sistema central funcionaba como: generador de pantalla, corredor de script, generador de informes, acceso a datos y capa de transporte.

Segmentar la lógica central y la lógica personalizada es un desafío difícil. Sin embargo, siempre sentimos que era mejor tener un sistema central ejecutando múltiples clientes, en lugar de tener múltiples copias del sistema ejecutándose para cada cliente.

Sam Goldberg
fuente
Gracias por la respuesta. Me gusta la idea de tener campos adicionales en un diccionario. Esto nos permitiría tener una definición única de una entidad y poner todas las cosas específicas del cliente en un diccionario. Sin embargo, no estoy seguro de si hay una buena manera de hacerlo funcionar con nuestro contenedor ORM (Entity Framework). Y tampoco estoy seguro de si es realmente una buena idea tener un modelo de datos compartido globalmente en lugar de tener un modelo para cada característica / módulo.
aKzenT
2

Trabajé en un sistema más pequeño (20 kloc) y descubrí que la DI y la configuración son excelentes formas de gestionar las diferencias entre los clientes, pero no lo suficiente como para evitar bifurcar el sistema. La base de datos se divide entre una parte específica de la aplicación, que tiene un esquema fijo, y la parte dependiente del cliente, que se define a través de un documento de configuración XML personalizado.

Hemos mantenido una única rama en mercurial que está configurada como si fuera entregable, pero marcada y configurada para un cliente ficticio. Las correcciones de errores se incorporan a ese proyecto, y el nuevo desarrollo de la funcionalidad principal solo ocurre allí. Las versiones para clientes reales son ramificaciones de eso, almacenadas en sus propios repositorios. Hacemos un seguimiento de los grandes cambios en el código a través de números de versión escritos manualmente y rastreamos las correcciones de errores utilizando números de confirmación.

Dan Monego
fuente
¿distingue entre bibliotecas centrales que son iguales entre los clientes o bifurca el árbol completo? ¿Se fusiona regularmente desde la línea principal a las horquillas individuales? Y en caso afirmativo, ¿cuánto tiempo costó la rutina de fusión diaria y cómo evitó que este procedimiento se desmoronara cuando comienza la presión de tiempo (como siempre ocurre en un punto del proyecto)? Perdón por tantas preguntas: p
aKzenT
Bifurcamos todo el árbol, principalmente porque las solicitudes de los clientes son anteriores a la integridad del sistema de compilación. La fusión desde el sistema principal se realiza manualmente usando mercurial y otras herramientas, y generalmente se limita a errores críticos o actualizaciones de características de gran tamaño. Intentamos mantener las actualizaciones de grandes fragmentos poco frecuentes, tanto por el costo de la fusión como porque muchos clientes alojan sus propios sistemas y no queremos ponerles costos de instalación sin proporcionarles valor.
Dan Monego
Tengo curiosidad: el mercurial IIRC es un DVCS similar a git. ¿Notó alguna ventaja al hacer estas fusiones entre sucursales en comparación con Subversion u otro VCS tradicional? Acabo de terminar un proceso de fusión muy doloroso entre 2 ramas desarrolladas completamente separadas usando subversión y estaba pensando si hubiera sido más fácil si usáramos git.
aKzenT
La fusión con mercurial ha sido mucho, mucho más fácil que la fusión con nuestra herramienta anterior, que era Vault. Una de las principales ventajas es que mercurial es realmente bueno para poner el historial de cambios al frente y al centro de la aplicación, lo que facilita el seguimiento de lo que se hizo dónde. La parte más difícil para nosotros fue mover las ramas existentes: si está fusionando dos ramas, mercurial requiere que ambas tengan la misma raíz, por lo que la configuración requiere una última fusión completamente manual.
Dan Monego
2

Me temo que no tengo experiencia directa del problema que usted describe, pero tengo algunos comentarios.

La segunda opción, reunir el código en un repositorio central (tanto como sea posible), y diseñar arquitecturas para la personalización (de nuevo, tanto como sea posible) es casi seguro el camino a largo plazo.

El problema es cómo planea llegar allí y cuánto tiempo llevará.

En esta situación, probablemente esté bien (temporalmente) tener más de una copia de la aplicación en el repositorio a la vez.

Esto le permitirá pasar gradualmente a una arquitectura que admita directamente la personalización sin tener que hacerlo de una sola vez.

William Payne
fuente
2

El segundo enfoque parece ser más elegante, pero tenemos muchos problemas sin resolver en este enfoque.

Estoy seguro de que cualquiera de esos problemas se puede resolver, uno tras otro. Si se atasca, pregunte aquí o en SO sobre el problema específico.

Como otros han señalado, tener una base de código central / un repositorio es la opción que debería preferir. Intento responder tu pregunta de ejemplo.

Por ejemplo: cómo maneja los cambios / adiciones en su modelo / base de datos. Estamos usando .NET con Entity Framework para tener entidades fuertemente tipadas. No veo cómo podemos manejar las propiedades que se requieren para un cliente pero que son inútiles para otro cliente sin saturar nuestro modelo de datos.

Hay algunas posibilidades, todas las he visto en sistemas del mundo real. Cuál elegir depende de su situación:

  • vivir con el desorden hasta cierto punto
  • introduzca una tabla "CustomAttributes" (que describe nombres y tipos) y "CustomAttributeValues" (para los valores, por ejemplo, almacenados como una representación de cadena, incluso si son números). Eso permitirá agregar dichos atributos en el momento de la instalación o el tiempo de ejecución, teniendo valores individuales para cada cliente. No insista en que cada atributo personalizado se modele "visiblemente" en su modelo de datos.

  • ahora debería estar claro cómo usar eso en el código: tenga solo un código general para acceder a esas tablas y un código individual (tal vez en una DLL de complemento separada, que depende de usted) para interpretar esos atributos correctamente

  • Otra alternativa es dar a cada tabla de entidad un campo de cadena grande donde puede agregar una cadena XML individual.
  • intente generalizar algunos conceptos, para que puedan reutilizarse más fácilmente en diferentes clientes. Recomiendo el libro de Martin Fowler " Patrones de análisis ". Aunque este libro no trata sobre la personalización de software en sí, también puede ser útil para usted.

Y para un código específico: también puede intentar introducir un lenguaje de secuencias de comandos en su producto, especialmente para agregar secuencias de comandos específicas del cliente. De esa manera, no solo crea una línea clara entre su código y el código específico del cliente, sino que también puede permitir que sus clientes personalicen el sistema en cierta medida por sí mismos.

Doc Brown
fuente
El problema que veo al agregar CustomAttributes o columnas XML para almacenar propiedades personalizadas es que tienen capacidades muy limitadas. Por ejemplo, ordenar, agrupar o filtrar en función de estos atributos es muy difícil. Una vez trabajé en un sistema que usaba una tabla de atributos extra y cada vez es más complejo mantener y manejar estos atributos personalizados. Por estas razones, estaba pensando en lugar de poner estos atributos como columnas en una tabla adicional que se asigna 1: 1 al original. Sin embargo, el problema sobre cómo definir, consultar y administrar estos sigue siendo el mismo.
aKzenT
@aKzenT: la personalización no es gratuita, tendrá que cambiar la facilidad de uso frente a la personalización. Mi sugerencia general es que no introduzca dependencias en las que la parte central del sistema depende de ninguna manera de cualquier parte personalizada, solo al revés. Por ejemplo, al presentar estas "tablas adicionales" para el cliente 1, ¿puede evitar implementar esas tablas y el código relacionado para el cliente 2? Si la respuesta es sí, entonces la solución está bien.
Doc Brown
0

Solo he construido una de esas aplicaciones. Yo diría que el 90% de las unidades vendidas se vendieron tal cual, sin modificaciones. Cada cliente tenía su propia máscara personalizada y nosotros entregamos el sistema dentro de esa máscara. Cuando entró un mod que afectó las secciones principales, intentamos usar la ramificación IF . Cuando entró el mod # 2 para la misma sección, cambiamos a la lógica CASE que permitía una futura expansión. Esto parecía manejar la mayoría de las solicitudes menores.

Cualquier otra solicitud personalizada menor se manejó implementando la lógica del caso.

Si los mods fueran dos radicales, construimos un clon (incluir por separado) y envolvemos un CASO a su alrededor para incluir el módulo diferente.

Las correcciones de errores y modificaciones en el núcleo afectaron a todos los usuarios. Probamos exhaustivamente en desarrollo antes de pasar a producción. Siempre enviamos notificaciones por correo electrónico que acompañaban cualquier cambio y NUNCA, NUNCA, NUNCA publicamos cambios de producción los viernes ... NUNCA.

Nuestro entorno era Classic ASP y SQL Server. NO éramos una tienda de códigos de espagueti ... Todo era modular usando Incluye, Subrutinas y Funciones.

Michael Riley - AKA Gunny
fuente
-1

Cuando me pidan que comience el desarrollo de B, que comparte el 80% de la funcionalidad con A, haré lo siguiente:

  1. Clone A y modifíquelo.
  2. Extraiga la funcionalidad que comparten A y B en C, que utilizarán.
  3. Haga que A sea lo suficientemente configurable para satisfacer las necesidades de B y de sí mismo (por lo tanto, B está incrustado en A).

Elegiste 1, y no parece encajar bien con tu situación. Tu misión es predecir cuál de 2 y 3 es mejor.

Moshe Revah
fuente
1
Suena fácil, pero ¿cómo hacer esto en la práctica? ¿Cómo hacer que su software sea tan configurable sin saturarlo con "if (customer1)", que se vuelve imposible de mantener después de algún tiempo?
aKzenT
@aKzenT Es por eso que dejé 2 y 3 para que elijas. Si el tipo de cambios necesarios para hacer que el proyecto del cliente1 respalde las necesidades del cliente2 a través de la configuración hará que el código no se pueda mantener, entonces no haga 3.
Moshe Revah
Prefiero hacer "if (opción1)" en lugar de "if (cliente1)". De esta manera, con N opciones, puedo gestionar muchos posibles clientes. Por ejemplo, con N opciones booleanas puede administrar 2 ^ N clientes, teniendo solo N 'si' ... imposible de administrar con la estrategia "if (customer1)" que requeriría 2 ^ N 'if'.
Fil