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
39
40
41
42
43
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("…") Obtem chave no AppSettings.json da aplicação
Dp.Observability.Log("…") Imprime log persoanlizado

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
29
30
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
28
29
30
31
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 October 26, 2023 (795e8af6)