Estou pensando em arquitetar uma nova solução que seja muito modular por natureza e gostaria de criar uma estrutura que suporte esse design para permitir fácil expansão futura, separação clara de preocupações, licenciamento por módulo, etc. encontrados na web sobre aplicativos modulares ou compostos são centrados na interface do usuário, focados no Silverlight, WPF etc. No meu caso, estou desenvolvendo um aplicativo de serviço WCF que será consumido por outros desenvolvedores que trabalham em vários projetos de interface do usuário.
O objetivo do meu projeto é criar uma fonte centralizada de lógica de negócios / domínio para dar suporte a vários aplicativos de negócios que atualmente duplicam processos, regras etc. E embora nem todos os benefícios da modularidade sejam alcançados, eu gostaria de aproveitar o oportunidade de estabelecer uma estrutura que aproveite essas partes das quais podemos tirar proveito.
Comecei o design observando a API exposta pelo aplicativo de serviço. É claro que os serviços podem ser segregados ao longo das linhas modulares que eu estava considerando. Por exemplo, terei serviços FinanceService, InventoryService, PersonnelService etc. que agrupam minhas operações de serviço para fornecer alta coesão na API enquanto mantêm o acoplamento baixo, para que os clientes tenham apenas que consumir os serviços pertinentes ao seu aplicativo.
Faz sentido para mim que eu possa ter módulos separados para cada um desses serviços, como MyApp.Finance, MyApp.Inventory, My.Personnel e assim por diante. Preocupações transversais e tipos compartilhados estariam no assembly compartilhado MyApp. A partir daqui, fico um pouco amarrado.
(Ah, devo mencionar que usarei um contêiner de IoC para injeção de dependência para manter o aplicativo fracamente acoplado. Não vou mencionar qual, porque não quero abrir a caixa de Pandora!)
No MyApp.ServiceHost, vou criar um arquivo host de serviço (.svc) correspondente a cada módulo, FinanceService.svc, por exemplo. O host de serviço precisa do nome do serviço que corresponda às informações no arquivo de configuração que contém a interface que define o contrato de serviço. A configuração de IoC é usada para mapear a implementação concreta da interface a ser usada.
1. A camada de serviço deve implementar a API e delegar nos módulos ou os módulos devem ser independentes (na medida em que contêm tudo relacionado a esse módulo, incluindo a implementação do serviço)?
Uma maneira de abordar o problema é ter um "módulo" MyApp.Services que contém a implementação dos contratos de serviço. Cada classe de serviço simplesmente delega para outra classe no módulo apropriado que contém a lógica do domínio para a operação. Por exemplo, a classe FinanceService WCF no MyApp.Services delega para outra interface que é implementada no módulo Finanças para executar a operação. Isso me permitiria manter uma fachada de serviço fina e 'plug-in' a implementação para a implementação do serviço e eliminar a necessidade de os módulos se preocuparem com o WCF, por exemplo.
Por outro lado, talvez seja preferível ter cada módulo independente, pois ele possui a interface e a implementação. O host de serviço refere-se à interface do contrato de serviço encontrada no módulo e o IoC está configurado para usar também a implementação apropriada do módulo. Isso significa que a adição de um novo módulo pode ser feita sem alterações na camada de serviço, exceto a adição de um novo arquivo .svc e informações de configuração da IoC.
Estou pensando no impacto se eu mudar do WCF padrão para uma interface de serviço RESTful ou se for para serviços RIA ou algo assim. Se cada módulo contiver a implementação do contrato de serviço, tenho que fazer alterações em todos os módulos se alterar a tecnologia ou a abordagem do serviço. Mas, se a fachada é seu próprio módulo, então só tenho que trocar essa parte para fazer uma alteração. Os módulos teriam que implementar um conjunto diferente de contratos (interfaces), possivelmente definido no assembly compartilhado ???
2. Qual é a melhor maneira de lidar com o compartilhamento de recursos entre módulos e / ou dependências entre módulos?
Tome, por exemplo, uma operação de recebimento. À primeira vista, faz sentido que isso entre no módulo Inventário, pois receber mercadorias é uma função de inventário. No entanto, há também um aspecto financeiro, pois precisamos gerar um recibo e autorizar o pagamento.
Por um lado, eu esperaria usar algum tipo de eventos / mensagens do domínio para comunicar a operação. O módulo Inventário gera um GoodsReceivedEvent que é tratado pelo módulo Financeiro para gerar o Recebimento e iniciar o processo de pagamento. No entanto, isso significa que o módulo Financeiro precisa saber sobre os itens de estoque que foram recebidos. Posso simplesmente consultá-los por ID, mas e se eu precisar de informações adicionais para o Recibo, como nome e / ou descrição, custo unitário etc.? Faz sentido que cada módulo tenha sua própria versão de um item de inventário projetado para atender às necessidades desse módulo? Nesse caso, o módulo Financeiro precisaria executar sua própria pesquisa nos itens de estoque ao manipular o GoodsReceivedEvent.
Usei esse exemplo de propósito, porque trabalhei muito com vários sistemas ERP e sei que eles são projetados com esse tipo de modularidade - simplesmente não sei como. Também não o mencionei explicitamente acima, mas prefiro seguir os princípios de Design Orientado a Domínio na arquitetura da solução e acredito que esse tipo de modularidade se encaixa nessa área.
Qualquer ajuda para envolver minha cabeça nisso é muito apreciada.
— SonOfPirate
Desculpe. Não consigo encontrar uma pergunta em todo o pensamento. Qual é a questão?
Procure os pontos de interrogação. Existem vários pontos em que ofereço opções, mas não presumo uma solução e várias perguntas específicas para ajudar a guiar o processo no caminho certo.
@SonOfPirate: Desculpe. Eu esperava que você dedicasse algum tempo para destacar ou enfatizar as perguntas para que outras pessoas pudessem ajudá-lo.
— S.Lott 12/05
Eu concordo com S.Lott. Sinto muito, mas isso não é uma pergunta, é um fluxo de consciência. Podemos ajudá-lo muito melhor se você condensar isso em uma ou duas perguntas focadas e tentar separar as preocupações de arquitetura dos detalhes da implementação.
@SonOfPirate: "Arquitetura" trata da estrutura e comunica essa estrutura aos outros desenvolvedores. Começa com a descrição. Ele deve atingir um nível de clareza e transparência que permita que outros desenvolvedores o obtenham totalmente e sigam os princípios de design de arquitetura em tudo o que fazem.
Faz sentido que cada módulo tenha sua própria versão de um item de inventário projetado para atender às necessidades desse módulo? Nesse caso, o módulo Financeiro precisaria executar sua própria pesquisa nos itens de estoque ao manipular o GoodsReceivedEvent. Onde traço a linha entre modularidade e a necessidade de compartilhar informações?
Você sempre terá que aceitar trocas. É tudo o que há para dizer às suas perguntas. É uma boa prática manter as entidades em um assembly separado; muitos aplicativos as mantêm em uma biblioteca compartilhada. Mas isso significa que não há limites lógicos de negócios (físicos). Executar suas próprias pesquisas significa muito código redundante que eu implementaria apenas se você tiver requisitos especiais nos dois módulos. Obviamente, olhando do ponto de vista arquitetural, seu Módulo Financeiro e seu Módulo de Inventário precisam compartilhar uma dependência de qualquer maneira. O módulo financeiro provavelmente depende do módulo de estoque. Se os requisitos permitirem que você deixe um módulo depender de outro módulo, eu diria que não há problema em encapsular as entidades nos módulos aos quais eles mais pertencem. Vocês' Precisamos encontrar um bom equilíbrio entre distinção física (dependências compartilhadas) e manutenção. Muitas dependências compartilhadas podem causar um pesadelo de manutenção nas versões. Além disso, com um ORM, haverá problemas se você desejar mapear novas relações para entidades existentes ou estendê-las a partir de módulos que dependem do módulo que contém a entidade.
Se você não usar o ORM, lembre-se de que todos os seus módulos provavelmente compartilham o mesmo banco de dados de qualquer maneira, como costuma ser o caso. Você poderia usar isso como um terreno comum.
Você também pode manter os dados simples (em termos de CSV / conjuntos de resultados, por exemplo) e usar um serviço central de mensagens para notificar os módulos que se registraram sobre novos dados, juntamente com algumas metainformações. Isso significa que não há dependências reais, mas sacrifica tipos de segurança e contratos e pode haver problemas com dados malformados. Eu acho que é quantos sistemas grandes de ERP fazem isso.
Então, resumindo, você terá que decidir que tipo de interface você deseja. Mais modularidade leva a menos contratos e vice-versa.
Provavelmente, eu uso uma biblioteca compartilhada para meus objetos de dados e um serviço central de mensagens com um padrão de publicação de subcategoria, que parece a melhor solução para mim.
Também concordo com isso - sistema de mensagens + pub / sub e bibliotecas compartilhadas para contratos dto (repositório interno de nuget). Fiquei me perguntando as mesmas perguntas como @SonOfPirate até este artigo colocá-lo em perspectiva para mim: msdn.microsoft.com/en-us/library/bb245678.aspx