SendGrid

The goal of this scenario is to have a situation closer to the real world where we will have an “Order” microservice and we want to send emails to each order created.

Important

The following example uses SendGrid, but you can use the library you want, the focus at this point is on understanding how Devprime enables the use of extensions and how to integrate them into your project without hurting the established architectural principles, ensuring separation of responsibilities and decoupling.

Creating a microservice to use in the example

We will use a microservice based on the Devprime platform

Type the command below in a directory of your choice, and then open the project in Visual Studio, VSCODE, or your preferred IDE:

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

Go to the root folder of the project: .\order\

Domain Implementations

We’re going to include a field of Email in our aggregate root domain layer. Open the existing class Order.cs in the path code .\src\core\domain\aggregates\order\Order.cs, include the property as highlighted below (line 6), and save the changes:

 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);
        }
    }
}

Accelerating Deployments with the CLI

The next step will be to use the CLI to speed up most of the necessary implementations and for that type the command below in your terminal:

dp init

The CLI requests authorization to change the necessary files of the solution, in this case we will type A to authorize all the files.

Starting SendGrid Setup

Change the “Devprime_Custom” key setting as shown in the example below:
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"
}

Adding an extension

We’re now going to include the “MailService” class that will be our extension, via the following Devprime CLI command.

1
dp add extensions MailService

The CLI requests another authorization to change the required files of the solution, press A to authorize all the files.

The following image shows the MailService class and its IMailService Interface, whose responsibility will be to implement the integrations with the SendGrid API.

It also shows the Extensions class and the IExtensions interface whose purpose is to be a proxy that allows the Devprime Pipeline execution context to access all extensions available in the application via dependency injection.

Devprime How To Extensions Key

When running the command to include the extension, notice that the CLI also generated its injection in the “Extensions” class: 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; }
}

Adding the Reference to the SendGrid Library

We’re going to include the SendGrid library in our project, remember to set the installation target for the “Devprime.Extensions” project. code .\src\adapters\extensions\Devprime.Extensions.csproj

Via dotnet CLI

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

Also run dotnet build in the project. (Remember to be in the root folder of the project .\order\):

dotnet build

Implementing the SendGrid integration

We’re going to change the “IMailService” interface code .\src\core\application\interfaces\adapters\extensions\IMailService.cs, including the following method signature:

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

We also need to implement the method in the “MailService” class
code .\src\adapters\extensions\mailservice\MailService.cs, as per the following model:

 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;
    }
}

Let’s better understand some existing features in the code above:

Code Description
Dp.Settings.Default("…") Get key in the app AppSettings.json
Dp.Observability.Log("…") Prints personalized log

Implemented the extension adapter call

Our scenario involves a domain event called “OrderCreated” and associated with it we have an EventHandler called “OrderCreatedEventHandler”, which has a call to the Devprime State Adapter for data persistence (line 11) and emits an event external to the application (line 23).
Path: 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;
    }
}

Let’s change the code of our EventHandler according to the model below (see highlighted lines):

 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;
    }
}

Testing the app

Now that we have the necessary implementations, let’s run the microservice via the command below:

Windows

.\run.ps1

Linux/Mac

.\run.sh

In the browser, go to the link: https://localhost:5001/swagger, we will use the POST method to create a new order:

Devprime How To Extensions Key`

Use the Request Body below:

 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
    }
  ]
}

You should receive the email as configured in the application.

Humanizer

Last modified November 20, 2024 (61099f59)