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 with each order created.

Important

The following example uses SendGrid, but you can use any 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 architecture 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 following command 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 at the domain layer. Open the existing Order.cs class in the path code .\src\core\domain\aggregates\order\Order.cs, add 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);
        }
    }
}

Speeding up implementations 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 asks for authorization to change the necessary files of the solution, in this case we will type A to authorize all 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 are 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 from the solution, press A to authorize all files.

The following image shows the MailService class and its IMailService Interface, which will be responsible for implementing 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, note 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 Integration with SendGrid

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 take a closer look at some of the features that exist in the code above:

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

Implemented extension adapter call

Our scenario involves a domain event called “OrderCreated” and associated with it is 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 application

Now that we have the necessary implementations, let’s run the microservice using 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 April 11, 2024 (cc33f7e6)