SendGrid

O objetivo deste cenário é termos uma situação mais próxima do mundo real onde teremos um microsserviço de “Order” e queremos enviar e-mails a cada pedido criado.

Importante

O exemplo a seguir utiliza o SendGrid, mas você pode utilizar a biblioteca que desejar, o foco neste momento é o entendimento de como o DevPrime habilita o uso de extensões e como integrá-las em seu projeto sem ferir os princípios de arquitetura estabelecidos, garantindo separação de responsabilidades e desacoplamento.

Criando um microserviço para utilizar no exemplo

Nós utilizaremos um microsserviço baseado na plataforma DevPrime

Digite o comando abaixo em um diretório de sua preferência e em seguida abra o projeto no Visual Studio, VSCODE ou sua IDE de preferência:

dp new order --state mongodb --stream rabbitmq --marketplace order

Acesse a pasta raiz do projeto: .\order\

Implementações de domínio

Nós vamos incluir um campo de Email em nosso aggregate root na camada de domínio. Abra a classe Order.cs existente no caminho code .\src\core\domain\aggregates\order\Order.cs, inclua a propriedade como segue destacado abaixo (linha 6) e salve as alterações:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace Domain.Aggregates.Order;
public class Order : AggRoot
{
    public string CustomerName { get; private set; }
    public string CustomerTaxID { get; private set; }
    public string Email { get; private set; }
    public IList<Item> Itens { get; private set; }
    public double Total { get; private set; }
    public void AddItem(Item item)
    {
        if (item != null && Itens != null)
        {
            var myItens = Itens.Where(p => p.SKU == item.SKU).FirstOrDefault();
            if (myItens != null)
                myItens.Sum(item.Amount);
            else
                Itens.Add(item);
        }
    }
}

Acelerando implementações com o CLI

O próximo passo será utilizar o CLI para acelerar grande parte das implementações necessárias e para isso digite o comando abaixo em seu terminal:

dp init

O CLI solicita autorização para alterar os arquivos necessários da solution, neste caso vamos digitar A para autorizar todos os arquivos.

Iniciando a configuração do SendGrid

Altere a configuração da chave “DevPrime_Custom” conforme exemplo abaixo:
code .\src\app\appsettings.json

1
2
3
4
5
6
"DevPrime_Custom": {
    "sendgrid.ssettings.apikey": "API_KEY_SENDGRID",
    "sendgrid.from.email": "YOUR_EMAIL_REGISTERED_AT_SENDGRID",
    "sendgrid.from.name": "YOUR_NAME",
    "stream.orderevents": "orderevents"
}

Adicionando uma extension

Nós vamos incluir agora a classe “MailService” que será a nossa extension, através do seguinte comando do DevPrime CLI.

1
dp add extensions MailService

O CLI solicita outra autorização para alterar os arquivos necessários da solution, tecle A para autorizar todos os arquivos.

A imagem a seguir mostra a classe MailService e sua Interface IMailService, cuja responsabilidade será implementar as integrações com a API do SendGrid.

Ela mostra também a classe Extensions e a interface IExtensions cuja finalidade é ser um proxy que permite ao contexto de execução do DevPrime Pipeline, acessar todas as extensions disponíveis na aplicação via injeção de dependência.

DevPrime How To Extensions Key`

Ao rodar o comando para inclusão da extension, note que o CLI gerou também sua injeção na classe “Extensions”: code .\src\adapters\extensions\Extensions.cs

1
2
3
4
5
6
7
8
9
public class Extensions : IExtensions
{
    public Extensions(IMailService mailService)
    {
        MailService = mailService;
    }

    public IMailService MailService { get; }
}

Adicionando a referência para a biblioteca SendGrid

Nós vamos incluir a biblioteca SendGrid em nosso projeto, lembre-se de definir o destino de instalação para o projeto “DevPrime.Extensions”. code .\src\adapters\extensions\DevPrime.Extensions.csproj

Via dotnet CLI

dotnet add .\src\adapters\extensions\DevPrime.Extensions.csproj package SendGrid

Rode também o dotnet build no projeto. (Lembre-se de estar na pasta raíz do projeto .\order\):

dotnet build

Implementando a integração com SendGrid

Nós vamos alterar a interface “IMailService” code .\src\core\application\interfaces\adapters\extensions\IMailService.cs, incluindo a assinatura de método a seguir:

1
2
3
bool SendMail(string subject,
              string plainText,
              string htmlMessage);

Nós também precisamos implementar o método na classe “MailService” code .\src\adapters\extensions\mailservice\MailService.cs, conforme o modelo a seguir:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Net;

namespace DevPrime.Extensions.MailService;
public class MailService : DevPrimeExtensions, IMailService
{
    private string ApiKey { get; set; }
    private string FromEmail { get; set; }
    private string FromName { get; set; }

    public MailService(IDpExtensions dp) : base(dp)
    {
        ApiKey = Dp.Settings.Default("sendgrid.ssettings.apikey");
        FromEmail = Dp.Settings.Default("sendgrid.from.email");
        FromName = Dp.Settings.Default("sendgrid.from.name");
    }

    public bool SendMail(string email, string name, string plainText)
    {
        var Client = new SendGridClient(ApiKey);
        var From = new EmailAddress(FromEmail, FromName);
        var Subject = "Thank you to shopping at DpCommerce";
        var To = new EmailAddress(email, name);
        var PlainTextContent = plainText;

        var msg = MailHelper.CreateSingleEmail(From, To, Subject, PlainTextContent, null);
        var response = Client.SendEmailAsync(msg).Result;
        if (!response.StatusCode.Equals(HttpStatusCode.OK) && !response.StatusCode.Equals(HttpStatusCode.Accepted))
        {
            Dp.Observability.Log($"[Not possible to send message. Details: {response.StatusCode} - {response.Body.ReadAsStringAsync().Result} - {response.Headers}]");

            return false;
        }

        return true;
    }
}

Vamos entender melhor alguns recursos existentes no código acima:

Code Description
Dp.Settings.Default("…") Realiza uma chamada ao AppSettings.json da aplicação em busca da chave passada via prâmetro.
Dp.Observability.Log("…") Realiza uma entrada de log de desenvolvedor, este pode ser ocultado do log via AppSettings.json caso necessário.

Implementado a chamada ao adapter de extension

O nosso cenário envolve um evento de domínio chamado “OrderCreated” e associado a ele temos um EventHandler chamado “OrderCreatedEventHandler”, que possui uma chamada ao DevPrime State Adapter para persistência dos dados (linha 11) e emite um evento externo à aplicação (linha 23).
Caminho: code .\src\core\application\eventhandlers\order\OrderCreatedEventHandler.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace Application.EventHandlers.Order;
public class OrderCreatedEventHandler : EventHandler<OrderCreated, IOrderState>
{
    public OrderCreatedEventHandler(IOrderState state, IDp dp) : base(state, dp) { }

    public override dynamic Handle(OrderCreated orderCreated)
    {
        var success = false;
        var order = orderCreated.Get<Domain.Aggregates.Order.Order>();

        Dp.State.Order.Add(order);

        var destination = Dp.Settings.Default("stream.orderevents");
        var eventName = "OrderCreated";
        var eventData = new OrderCreatedEventDTO()
        {
            ID = order.ID,
            CustomerName = order.CustomerName,
            CustomerTaxID = order.CustomerTaxID,
            Total = order.Total
        };

        Dp.Stream.Send(destination, eventName, eventData);

        success = true;
        return success;
    }
}

Vamos alterar o código do nosso EventHandler conforme modelo abaixo (vide linhas destacadas):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace Application.EventHandlers.Order;
public class OrderCreatedEventHandler : EventHandlerWithStateAndExtensions<OrderCreated, IOrderState, IExtensions>
{
    public OrderCreatedEventHandler(IOrderState state, IExtensions extension, IDp dp) : base(state, extension, dp) { }

    public override dynamic Handle(OrderCreated orderCreated)
    {
        var success = false;
        var order = orderCreated.Get<Domain.Aggregates.Order.Order>();

        Dp.State.Order.Add(order);

        var destination = Dp.Settings.Default("stream.orderevents");
        var eventName = "OrderCreated";
        var eventData = new OrderCreatedEventDTO()
        {
            ID = order.ID,
            CustomerName = order.CustomerName,
            CustomerTaxID = order.CustomerTaxID,
            Total = order.Total
        };
        Dp.Stream.Send(destination, eventName, eventData);

        success = Dp.Extensions.MailService.SendMail(order.Email, order.CustomerName, "Your content here");
        return success;
    }
}

Testando a aplicação

Agora que temos as implementações necessárias, vamos rodar o microsserviço através do comando abaixo:

Windows

.\run.ps1

Linux / Mac

.\run.sh

No browser, acesse o link: https://localhost:5001/swagger, nós vamos utilizar o método POST para criar uma nova order:

DevPrime How To Extensions Key`

Utilize o Request Body abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "customerName": "YOUR_NAME",
  "customerTaxID": "12345678910",
  "email": "TYPE_YOU_EMAIL",
  "itens": [
    {
      "description": "TV",
      "amount": 1,
      "sku": "TV001",
      "price": 1000.00
    }
  ]
}

Você deverá receber o e-mail conforme configurado na aplicação.

Mais artigos sobre extensions

Humanizer

Última modificação April 5, 2022 (ce397e7)