Seu serviço deve rodar sob qualquer host, ou sem host (como uma dependência de library), com qualquer tecnologia, e não deve precisar de modificações quando novas tecnologia são lançadas.
Óbvio que estamos falando de algo a perseguir e não uma regra absoluta e/ou intransigente.
História
Embora a esmagadora maioria dos meus leitores talvez sequer tenha conhecido WCF, preciso dar um passo atrás e falar do Spring.NET antes do WCF: ” Build, Ship, and Run Any App, Anywhere” embora esse seja o slogan do docker, era facilmente cabível para Serviços baseados no Spring.NET com o pacote Spring.Services.
Só era necessário 1 contrato (Interface), cujas entradas e saídas dos métodos fossem serializáveis. Somado a uma pá de configurações, você escolhia como publicar o serviço, embarcado ou remoto.
Nada de anotações/atributos, nada disso era necessário. Somente 1 contrato, sua implementação e algumas configurações.
Essa ideia não é minha, nem do Spring
Agnostic Service é um pattern que comecei a usar antes de saber o nome. Só vi catalogado como um pattern or volta de 2010, num momento muito SOA do nosso mercado. Eu já conhecia e usava, mas essa foi a primeira vez que o vi com esse nome.
Sob a mesma ideia
Seguindo essa linha de raciocínio, era muito simples ver serviços realmente agnósticos, serviços que não dependiam de uma arquitetura de deployment X ou Y, dependiam apenas de outras interfaces que poderiam ser hospedados também remotamente usando arquiteturas X, Y ou Z.
Isso implica em dizer que seu negócio nunca poderá ser representado por um Web API. Seu serviço deve ser construído sem dependência da infraestrutura web, da mesma forma que não pode depender da infraestrutura do RabbitMQ, quando conectado a uma fila. Você até pode construir uma adapter na mão, mas eu prefiro implementações fornecidas junto com a infra de injeção de dependência.
Como realizar isso?
Um dos maiores desafios são as peculiaridades de cada tecnologia. Como identificar usuário atual, como tratar exceções corretamente, a dificuldade mora no contexto de cada host, que por sua vez possuem peculiaridades.
AOP
AOP é um aliado que uso há muito tempo e tem me servido a esse propósito. Aspectos que lidam com transações e conexão com banco, aspectos que lidam com exceções, runtimes e hosts específicos, como WCF, HTTP, MVC etc.
Configuração
Um dos pilares para assegurar essa abordagem é uma boa infraestrutura de configuração. Eu particularmente prefiro as configurações XML do Spring.NET ao código de um Startup.cs
SOLID
Desenvolver com SOLID é fundamental para conseguir esse nível de abstração. Quando um serviço depende da interface de outro, e estamos simultaneamente gerenciando isso via um robusto container de injeção de dependência, temos a possibilidade de injetarmos proxies para consumo remoto, da mesma forma como podemo injetar um host, in process, para o recebimento das requisições.
Conclusão
Não importa onde seu serviço esteja, e qual a estratégia de deployment realizada. Uma vez com um container (container IoC) bem configurável, é possível tomar a decisão de ter os 2 serviços no mesmo processo ou em processos separados, sem que isso afete seu código. Você deve estar pronto para tomar essa decisão a qualquer momento, principalmente quando não houver muito tempo.
Estratégias de computação distribuída devem poder ser aplicadas a qualquer momento, com a alteração de configurações apenas. Sua aplicação precisa ser resiliente a isso.
Quando digo “poder ser aplicada” significa que você precisa ter a possibilidade de usar, não necessariamente vai, e prioritariamente não fará. Mas se precisar, não deve custar caro. Eu lido com a não decisão. Eu não preciso assinar com sangue que uma comunicação será ou não remota. Eu lido com esse problema de forma que eu construo cada serviço possibilitando que seja consumido e hospedado remotamente, e deixo para tomar a decisão no último momento possível, ou deixo para não tomar decisão alguma antes de chegar em produção.
Ter essa flexibilidade custa mais, exige cuidados, mas elimina drasticamente o retrabalho e o custo de tomar uma decisão errada ao distribuir onde não era necessário distribuir, da mesma forma que não distribuir onde era necessário distribuir. Muitas vezes tomamos decisões importantes cedo demais.
No youtube downloader, projeto que temos aqui no site, já vimos isso, ao conectar serviços às filas.
Em 4 ou 5 steps, nenhum serviço sabe que quem o chama é uma abstração de fila, o serviço pode ser chamado em tanto um teste unitário, quanto plugado em uma fila.