Diseñar una aplicación de servicio modular

11

Estoy pensando en diseñar una nueva solución que sea muy modular por naturaleza y me gustaría crear una estructura que soporte ese diseño para permitir una fácil expansión futura, una clara separación de preocupaciones, licencias por módulo, etc. que se encuentran en la web sobre aplicaciones modulares o compuestas están centradas en UI, enfocadas en Silverlight, WPF, etc. En mi caso, estoy desarrollando una aplicación de servicio WCF que será consumida por otros desarrolladores que trabajan en varios proyectos de UI.

Antecedentes

El objetivo de mi proyecto es crear una fuente centralizada de lógica comercial / de dominio para admitir varias de nuestras aplicaciones comerciales que actualmente duplican procesos, reglas, etc. Y aunque no se lograrán todos los beneficios de la modularidad, me gustaría aprovechar oportunidad de establecer un marco que aproveche las partes que podemos aprovechar.

Comencé el diseño mirando la API expuesta por la aplicación de servicio. Está claro que los servicios se pueden segregar a lo largo de las líneas modulares que estaba considerando. Por ejemplo, tendré servicios de FinanceService, InventoryService, PersonnelService, etc. que agrupan mis operaciones de servicio para proporcionar una alta cohesión en la API y mantener el acoplamiento bajo para que los clientes solo tengan que consumir los servicios que sean pertinentes para su aplicación.

Para mí tiene sentido que luego pueda tener módulos separados para cada uno de estos servicios, como MyApp.Finance, MyApp.Inventory, My.Personnel, etc. Las preocupaciones transversales y los tipos compartidos estarían en el ensamblado compartido MyApp. Desde aquí me ato un poco.

(Oh, debo mencionar que usaré un contenedor IoC para la inyección de dependencia para mantener la aplicación acoplada libremente. ¡No mencionaré cuál porque no quiero abrir la caja de Pandora!)

En MyApp.ServiceHost, crearé un archivo de host de servicio (.svc) correspondiente a cada módulo, por ejemplo, FinanceService.svc. El host del servicio necesita el nombre del servicio que corresponde a la información en el archivo de configuración que contiene la interfaz que define el contrato de servicio. La configuración de IoC se usa para mapear la implementación concreta de la interfaz a usar.

1. ¿Debería la capa de servicio implementar la API y delegar en los módulos o deberían ser autónomos (en el sentido de que contienen todo lo relacionado con ese módulo, incluida la implementación del servicio)?

Una forma de abordar el problema es tener un "módulo" MyApp.Services que contenga la implementación de los contratos de servicio. Cada clase de servicio simplemente delega a otra clase en el módulo apropiado que contiene la lógica de dominio para la operación. Por ejemplo, la clase WCF FinanceService en MyApp.Services delega a otra interfaz que se implementa en el módulo de Finanzas para llevar a cabo la operación. Esto me permitiría mantener una fachada de servicio delgada y 'conectar' la implementación a la implementación del servicio y eliminar la necesidad de que los módulos se preocupen por WCF, por ejemplo.

Por otro lado, tal vez sea preferible que cada módulo sea autónomo ya que tiene la interfaz y la implementación. El host del servicio se refiere a la interfaz del contrato de servicio que se encuentra en el módulo y el IoC también está configurado para usar la implementación adecuada del módulo. Esto significa que se puede agregar un nuevo módulo sin cambios en la capa de servicio que no sea agregar un nuevo archivo .svc e información de configuración de IoC.

Estoy pensando en el impacto si cambio de WCF estándar a una interfaz de servicio RESTful o tal vez vaya a servicios RIA o algo así. Si cada módulo contiene la implementación del contrato de servicio, entonces tengo que hacer cambios en cada módulo si cambio la tecnología o el enfoque del servicio. Pero, si la fachada es su propio módulo, entonces solo tengo que cambiar esa parte para hacer un cambio. ¿Los módulos tendrían que implementar un conjunto diferente de contratos (interfaces), posiblemente definidos en el ensamblado compartido?

2. ¿Cuál es la mejor manera de manejar el intercambio de recursos entre módulos y / o dependencias entre módulos?

Tomemos, por ejemplo, una operación de recepción. A primera vista, tiene sentido que esto entre en el módulo de Inventario, ya que recibir mercancías es una función de inventario. Sin embargo, también hay un aspecto financiero en el que necesitamos generar un Recibo y autorizar el pago.

Por un lado, esperaría usar algún tipo de eventos / mensajes de dominio para comunicar la operación. El módulo de Inventario genera un Evento de Bienes Recibidos que es manejado por el módulo Financiero para generar el Recibo e iniciar el proceso de pago. Sin embargo, esto significa que el módulo Financiero necesita saber acerca de los artículos de inventario que se recibieron. Simplemente puedo referirme a ellos por ID, pero ¿qué sucede si necesito información adicional para el Recibo, como el nombre y / o la descripción, el costo unitario, etc.? ¿Tiene sentido que cada módulo tenga su propia versión de un artículo de inventario diseñado para satisfacer las necesidades de ese módulo? En ese caso, el módulo Financiero tendría que realizar su propia búsqueda de los artículos de inventario cuando maneje el Evento GoodsReceived.

...

Utilicé este ejemplo a propósito porque he trabajado mucho con varios sistemas ERP y sé que están diseñados con este tipo de modularidad. Simplemente no sé cómo. Tampoco lo menciono explícitamente anteriormente, pero prefiero seguir los principios del diseño impulsado por dominio en la arquitectura de la solución y creo que este tipo de modularidad encaja perfectamente en esa área.

Cualquier ayuda para entender esto es muy apreciada.

Hijo de pirata
fuente
1
Lo siento. No puedo encontrar una pregunta en todo el pensamiento. ¿Cuál es la pregunta?
S.Lott
1
Busca los signos de interrogación. Hay varios puntos en los que ofrezco opciones, pero no presumo una solución y varias preguntas específicas para ayudar a guiar el proceso por el camino correcto.
SonOfPirate
1
@SonOfPirate: Lo siento. Esperaba que te tomaras el tiempo de resaltar o enfatizar las preguntas para que otras personas pudieran ayudarte.
S.Lott
44
Estoy de acuerdo con S.Lott. Lo siento, pero esta no es una pregunta, es una corriente de conciencia. Podemos ayudarlo mucho mejor si condensa esto en una o dos preguntas específicas y trata de separar las preocupaciones arquitectónicas de los detalles de implementación.
Aaronaught
1
@SonOfPirate: "Arquitectura" se trata de estructurar y comunicar esa estructura a los otros desarrolladores. Comienza con la descripción. Debe alcanzar un nivel de claridad y transparencia que permita a otros desarrolladores "entenderlo" totalmente y seguir los principios de diseño arquitectónico en todo lo que hacen.
S.Lott

Respuestas:

2

¿Tiene sentido que cada módulo tenga su propia versión de un artículo de inventario diseñado para satisfacer las necesidades de ese módulo? En ese caso, el módulo Financiero tendría que realizar su propia búsqueda de los artículos de inventario cuando maneje el Evento GoodsReceived. ¿Dónde trazo la línea entre la modularidad y la necesidad de compartir información?

Siempre tendrá que aceptar compensaciones. Eso es todo lo que hay que decir a sus preguntas realmente. Ha sido una buena práctica mantener las entidades en un ensamblado separado, por lo que muchas aplicaciones las mantienen en una biblioteca compartida. Pero eso significa que no hay límites (físicos) lógicos para el negocio. Realizar sus propias búsquedas significa una gran cantidad de código redundante que solo implementaría si tiene requisitos especiales en ambos módulos. Por supuesto, viéndolo desde un punto de vista arquitectónico, su Módulo Financiero y su Módulo de Inventario tienen que compartir una dependencia de todos modos. El módulo financiero probablemente depende del módulo de inventario. Si los requisitos le permiten dejar que un módulo dependa de otro módulo, entonces diría que está bien encapsular las entidades dentro de los módulos a los que pertenecen más. Usted' Necesitará encontrar un buen equilibrio entre la distinción física (dependencias compartidas) y el mantenimiento. Demasiadas dependencias compartidas podrían causar una pesadilla de mantenimiento de las versiones. Además, con un ORM habrá problemas si desea asignar nuevas relaciones a entidades existentes o extenderlas desde módulos que dependen del módulo que contiene la entidad.

Si no usa ORM, tenga en cuenta que todos sus módulos probablemente compartan la misma base de datos de todos modos, como suele ser el caso. Podrías usar eso como un terreno común.

También puede mantener los datos claros (en términos de CSV / conjuntos de resultados, por ejemplo) y usar un servicio de mensajería central para notificar a los módulos que se registraron sobre los nuevos datos junto con cierta metainformación. Eso significa que no hay dependencias reales, pero sacrifica la seguridad de los tipos y los contratos y puede haber problemas con los datos mal formados. Supongo que así es como lo hacen muchos grandes sistemas ERP.

En resumen, tendrá que decidir qué tipo de interfaz desea. Más modularidad conduce a menos contratos y viceversa.

Probablemente usaría una biblioteca compartida para mis objetos de datos y un servicio de mensajería central con un patrón de publicación secundaria, esa es la mejor solución para mí.

Halcón
fuente
También esté de acuerdo con esto: mensajería + pub / sub y bibliotecas compartidas para contratos dto (repositorio nuget interno). Me preguntaba las mismas preguntas que @SonOfPirate hasta que este artículo lo puso en perspectiva para mí: msdn.microsoft.com/en-us/library/bb245678.aspx
diegohb