fbpx
Do WCF para ASP.NET Core Web API com Docker no Linux 2/2
Publicado em: sábado, 1 de fev de 2020

Agora é hora de mostrar como podemos tirar proveito dos contratos (interfaces) existentes para promover essa migração. Esse é o momento em que a "SOPA de LETRINHAS" ou melhor, os design patterns fazem sentido. Graças a eles, essa migração que poderia ser dolorosa e desastrosa, se torna algo fácil. Não é simples, mas exige pouco esforço. Esses patterns permitirão que o cliente tenha APENAS A FACTORY alterada nessa transição.

É difícil conseguirmos montar um exemplo assim, em geral isso só acontece na vida real, longe das demos, longe das apresentações.

O mundo maravilhoso das demos é um mundo, antes de mais nada, experimental, isolado, com contexto reduzido.

Vídeo

Eu já havia colocado esse vídeo no final do primeiro post, mas resolvi colocar aqui também.

Recapitulando

Estamos falando de uma transição de um serviço WCF para o .NET Core. Do Windows para o Linux, com Docker.

Pra ser mais específico, de WCF para ASP.NET Core Web API.

Nessa jornada produzimos vários projetos:

Core

Separamos um core, dos demais artefatos (host e consumer). E há um sentido lógico nisso: Quando falamos em Agnostic Services, estamos aplicando padrões que viabilizem que seus serviços não tenham ciência da estratégia de deployment utilizada para hospedá-los.

No nosso caso, abri uma exceção, permitindo que o contrato fosse marcado com atributos do WCF (ServiceContractAttribute e OperationContractAttribute). Aceito esse tipo de exceção pois entendo que esses atributos embora produzam acoplamento, são dependências fracas que não produzem impacto no comportamento, dessa forma o serviço continua disponível para qualquer outra implementação de hosting, sem conhecê-las.

WcfRefactoring.Contracts

Contem apenas a Interface do serviço ICalc. Se tivéssemos DTO's, estaria aqui também.

Esse projeto tem o papel de expressar todo o Contrato do Serviço. Entrada, saída, e dados.

Esse projeto não pode ter a implementação dos serviços, apenas suas interfaces.

WcfRefactoring.Service

Aqui temos a referência para o projeto de Contratos (WcfRefactoring.Contracts) e aqui implementamos o serviço.

Esse projeto não pode ter a contrato dos serviços, apenas suas implementações.

Pra que enfatizar tanto essa segmentação?

Se você fundir os 2 projetos, a mágica não acontece.

Mas qual mágica?

Você precisa ter a segurança de que distribuindo o assembly de contrato, ele não levará consigo a implementação dos contratos. A única forma de garantir isso é não distribuindo-as.

Acontece que se você enviar os 2 assemblies ou se seu assembly tem as duas coisas, qualquer um com um ILSpy é capaz de analisar seu assembly, identificando tudo que há nele. Classes, métodos parâmetros e requisitos e até código e regras de negócio. O máximo que você consegue fazer é ofuscar (uma técnica de dá uma zoada no código após a compilação), mas ainda assim tem ferramentas pagas poderosíssimas destinadas a exatamente quebrar a ofuscação.

Assim se quer que alguém não tenha acesso, não entregue.

Mas o que nos faz não querer distribuir nosso código de negócio?

É simples: Gestão. Você quer poder evoluir e controlar o ciclo evolutivo. Controlar com clareza a evolução do seu próprio projeto, e para isso precisa lidar e gerenciar dependências e versões de dependência. Se todo mundo tem a implementação, seu esforço pode produzir inconsistências, visto que se seu código está presente em 10 lugares, é fácil se perder atualizando parte dessas dependências. Em alguns casos é até inviável atualizar todas. Mas se seu contrato não sofreu alteração, e você fez exatamente como proposto nesse post. Seu cliente não precisa lidar com a evolução do teu serviço, apenas quando o contrato de fato sofrer alteração.

Por isso segmentar contrato de implementação. Para que seus clientes tenham uma versão do contrato, sem ter uma versão da implementação.

WCF

Aqui temos os 2 exemplos de hosting e consumidor.

WcfRefactoring.NetFrameworkServer

Nesse exemplo eu demonstro como hospedar um serviço WCF com pouquíssimas linhas de código e pouquíssimo esforço. Claro, temos um app.config com algumas coisas a mais, mas é um padrão.

Aliás, se quisesse transformar esse projeto em um serviço Windows, usaria o Topshelf.

WcfRefactoring.NetFrameworkClient

Para o cliente, usamos o mínimo de configuração também.

Note que o papel do Factory é importantíssimo aqui. Ele quem absorve a complexidade de criação do serviço. Mais tarde, na migração, esse cara fará outra mágica.

Conclusão

Serviços WCF são mais fáceis de se hospedar e consumir do que a maioria dos projetos que esbarramos por aí. Isso se dá pela natureza de quem gosta desse tipo de serviço.

ASP.NET Core

Aqui temos nossa migração.

Reimplementamos o host do serviço com um API controller e fizemos uma mágica incrível com o factory aproveitando a interface.

WcfRefactoring.ASPNetCoreWebAPI

Esse é nosso serviço WCF, portado para o Controller Web API. Note que o controller não implementa a lógica, ele delega para a mesmíssima classe que implementava o serviço usado no WCF.

WcfRefactoring.NetFrameworkClient2

Aqui temos algo disruptivo e possivelmente complexo ao primeiro olhar.

Se você depende da interface do serviço, e se você usa factories ou injeção de dependência para entregar a implementação do cliente WCF para teu código. Você poderá tirar proveito dos mesmos elementos que eu crio aqui.

Se você não faz isso. Precisa chegar a esse nível de isolamento para poder tirar proveito.

E uma dica: É isso que eu faço em diversos clientes.

Eu crio uma força-tarefa para migrarmos de WCF para WCF, mas agora dependendo do contrato real, e não gerado por service references, instanciando o cliente WCF por meio de uma factory.

Sim, essa etapa é uma premissa que lhe trará segurança na sua migração.

Agora que você depende de uma factory e da interface do serviço ou apenas da interface (quando tem DI), você está pronto para usar AOP.

Para implementar AOP você precisa produzir um proxy para tua classe. Um proxy pode ser produzido com base em uma interface ( e nesse caso em geral é um proxy baseado em composição, onde o objeto target, é uma composição no proxy). Esse proxy intermedia a comunicação do mundo externo com a tua classe, permitindo que você adicione interceptadores. Que são modificadores de comportamento que são acionados a toda iteração com sua classe alvo.

Então no nosso caso temos a interface ICalc

using System;
using System.ServiceModel;

namespace WcfRefactoring
{
    [ServiceContract]
    public interface ICalc
    {
        [OperationContract]
        int Sum(int part1, int part2);
    }
}

 

Agora, no factoy temos a oportunidade de criar um proxy.

O proxy que escolhi é um Proxy de Interface, Sem Target.

Isso quer dizer que esse proxy e baseia na interface que eu passar para ele.

E, não terá um target, ou seja, não terá uma implementação real para realizar a tarefa.

A implementação real será substituida pelo comportamento injetado pelo interceptador (HttpDotNetCoreInterceptor) (linha 17 do lado direito).

O interceptor está abaixo:

using Castle.DynamicProxy;
using RestSharp;
using RestSharp.Authenticators;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Dynamic;

namespace WcfRefactoring.NetFrameworkClient2
{
    public class HttpDotNetCoreInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var client = new RestClient(GetEndpointUrl());

            var request = new RestRequest(GetPath(invocation), DataFormat.Json);

            int i = 0;
            foreach (var parameter in invocation.Method.GetParameters())
            {
                request.AddParameter(parameter.Name, invocation.GetArgumentValue(i++));
            }

            var response = client.Post(request);

            if (invocation.Method.ReturnType != typeof(void))
            {
                invocation.ReturnValue = Newtonsoft.Json.JsonConvert.DeserializeObject(response.Content, invocation.Method.ReturnType);
            }
        }

        private static string GetPath(IInvocation invocation)
        {
            Type proxyType = invocation.Method.DeclaringType;
            if (proxyType.IsInterface && proxyType.Name.StartsWith("I"))
            {
                return proxyType.Name.Substring(1).ToLower();
            }
            return proxyType.Name.ToLower();
        }

        private static string GetEndpointUrl()
        {
            return ConfigurationManager.AppSettings["dotnet-core-endpoint"] ?? throw new InvalidOperationException("The configuration dotnet-core-endpoint is not set on App.Config/Web.Config.");
        }
    }
}

 

O que está acontecendo aqui?

O DynamicProxy da Castle produziu para nós um proxy baseado na Interface, sem target. Nossa implementação de Interceptor fez o trabalho sujo de interceptar e fazer uma requisição HTTP com um backend, usando RestSharp pra isso. O retorno da requisição http foi desserializado e atribuído à propriedade ReturnValue da Invocation.

Esse não é um assunto novo. Só é um assunto esquecido. AOP é um termo que ficou no legado de estudo de muita gente, muita gente que sequer sabia da existência da Programação Orientada a Aspectos.

Conclusão

Escolher os patterns que vão, de fato, te ajudar é uma decisão que exige experiência para ser tomada.

Tudo começa com os objetivos de cada pattern e sobre quais problemas eles irão resolver.

Não são nada raros, pelo contrário, é comum, encontrarmos casos em que primeiro fazemos uma pequena transição (um passo sólido - baby step) adicionando alguns padrões que assegurarão uma transição mais sadia e menos traumática, viabilizando a transição para algo maior e o objetivo final.

Esse post tem a ideia de documentar algo que já fiz algumas vezes e estou fazendo novamente.

Esse é o tipo de projeto e o tipo de refactoring que não diz respeito ao negócio. Você pode fazer isso em qualquer contexto, em qualquer empresa, em qualquer projeto. É absolutamente técnico e por isso não exige sequer experiência no negócio, só nas tecnologias envolvidas nesse rolê todo.

0 comentários

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

[docker de a a z]

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.