From Concept to Code - Building a Pomo Assistant API

From Concept to Code - Building a Pomo Assistant API

This blog post walks through setting up a .NET 8 minimal API project, using pomos as an example target resource. "Pomos" here refer to blocks of time structured according to the Pomodoro time management technique.

The Pomo Assistant project is available on GitHub here, and the list of commits described in this post is available in the pull request here.

There are countless ways to implement an API. The way described in this post is just one among many. The post presents the first steps in setting up a new project, as such it intentionally excludes databases, deployments, security, and anything else "too complicated".

Pomodoro Technique

The Pomodoro Technique is a time management method that structures time into 25-minute-long blocks of uninterrupted and focused work, followed by 5-minute breaks.

This commit sets up the following class to model these blocks of time - pomos.

public sealed class Pomo(DateTime completedAt, Intent intent)
{
    public Guid Id { get; } = Guid.NewGuid();
    public DateTime CompletedAt { get; set; } = completedAt;
    public Intent Intent { get; set; } = intent;
    public string Description { get; set; } = string.Empty;
    public int DurationInMinutes => 25;
}

The class uses a primary constructor because completedAt and intent do not require any validation. The Description property has a default value so it can be set outside the constructor.

The Intent property belongs to an enumeration type. It is one of maintenance, exploration, or exploitation, intended as a broad classification of the pomo time. The terms exploration and exploitation are borrowed from reinforcement learning. Exploration refers to trying out new things. Exploitation refers to perfecting one's strengths. Maintenance describes pomos spent on upkeep activities such as taking a meal, hygiene, and leisure.

GUID stands for globally unique identifier. It's written as "Guid" due to Pascal case naming conventions as described here. According to the documentation, a GUID is a 128-bit integer that can be used across all computers and networks wherever a unique identifier is required.

CRUD Operations

CRUD stands for Create, Read, Update, Delete. It describes operations common to nearly all kinds of stored data.

The first commit sets up the pomo service interface and request/response models. The pomo service manages a collection of pomos and provides CRUD operations to operate on them. The models represent requests and responses to the service.

GetById and Search are two types of "Read" operations to retrieve individual pomos and pomos within a time range. Update and Patch are two types of "Update" operations. The former performs a full update while the latter updates an arbitrary subset of properties. It's possible to use Patch here because the Pomo class has no nullable properties.

The second commit sets up a test suite for the pomo service.

The setup method resets the pomo service before every test. This is necessary because the pomo service is stateful. The setup ensures that all tests start identically and do not interfere with each other.

The AutoData attribute is part of the AutoFixture library. It generates objects with random properties, which is useful here because the property values do not matter; only the fact that they change does.

The BeEquivalentTo method is part of the FluentAssertions library. In the code below, it ensures that all properties of request are present in result and hold the same values. This works because all properties that can be updated (request) are also returned for reading within the PomoModels.ReadModel class (result).

result.Should().BeEquivalentTo(request);

The third and final commit implements the pomo service and ensures that all tests pass.

Web API

One way to view web APIs is as a wrapper over internal services for communications over the internet. Within the PomoAssistant application, C# classes access the pomo service via the IPomoService interface. Externally, other applications access the pomo service via a web API.

The first commit sets up a web API project using the minimal API template. The second commit adds the pomo endpoints.

All methods in the PomoEndpoints class follow the same four-step pattern:

  1. Validate the request if necessary.
  2. Map the request if necessary.
  3. Call IPomoService.
  4. Return success/fail results.

The PomoEndpoints class avoids reusing the request models defined in PomoModels in favour of using its own model PomoRequest and doing the mapping. This approach has two benefits. Firstly, missing non-nullable properties are set to the default value, possibly leading to problems. This solution prevents that by having all properties in the PomoRequest be nullable and then validating they are non-null. Secondly, the PomoRequest can be shared between multiple methods: Create, Update, and Patch.

The instruction builder.Services.AddSingleton<IPomoService, PomoService>(); in the Program.cs file registers the pomo service for dependency injection as a singleton. This kind of service often works as scoped; however, this solution forgoes a database and makes the pomo service double as pomo storage. A scoped service would reset on every HTTP request, which is undesirable here. A singleton resets only when the application restarts.

Conclusion

The result of all the work is a pomo assistant API with the following auto-generated swagger UI accessible via https://localhost:7096/swagger/index.html

Read more