This is just a Hosted Service that runs with a timer, this means the task is run at intervals. For example every 60 seconds poll a service for changes. Hosted Service
is a generic term Microsoft uses as they state “Hosted Services are services/logic that you host within your host/application/microservice.” The Microsoft project template for this is Worker Service (using the BackgroundService base class) and other common names I’ve seen are Bots
, Worker Processes
, Workers
and my personal favorite Daemon
😈
non-HTTP workloads like messaging, background tasks, console applications etc. These types of applications don’t have the notion of an incoming HTTP request like a traditional ASP.NET/ASP.NET Core Web Application - docs.microsoft.com
Story
Consider the following events feed for a shopping cart below. These events are CRUD actions CART_CREATE
, CART_UPDATE
and CART_DELETE
. The following query string parameters are used from the json-server instance.
- The watermark is set with
id_gte=42
this means whereevents.id
>= 42 - Pagination is set with
_page=3
this means only show page 3 - Set the page size with
_limit=25
this means the page size is 25, ie: the maxevents
count will be 25
Verb | URI example |
---|---|
GET | http://localhost:3331/events?id_gte=42&_page=3&_limit=25 |
1 | { |
Task
Create a hosted service that fulfills the following acceptance criteria:
- consumes this feed and persists the CRUD event to a database as a
CREATE
,UPDATE
orDELETE
- a
watermark
needs to be used so events are not processed again - use
page
andsize
parameters - the service should run infinitely until the process is interupted with
ctrl+c
- the solution should be extensible
Solution
This is the simplest no frills solution, from here it could be adapted to inlude message queues and better separation of concerns. The code for this solution can be found at https://github.com/carlpaton/TimedHostedServiceDemo
TimedHostedService.Worker
Create a timed background task per the Microsoft documentation using the IHostedService interface. Note that the BackgroundService is a base class for implementing a long running IHostedService. So this would work but I’ve use the interface instead as that the example for a time hosted service at docs.microsoft.com.
In Program.cs use
HostBuilder
to configure the startup pipeline. UseConfigureServices
to registerTimedHostedService
withAddHostedService
. Additionally add console logging withConfigureLogging
and configuration withConfigureAppConfiguration
1 | public class TimedHostedService : IHostedService, IDisposable |
- Running the app now produces a timed event every 42 seconds and this is shown in the logs until
ctrl+c
is pressed.
Beauty!
1 | info: TimedHostedService.Worker.TimedHostedService[0] |
- Design a DDD-oriented microservice
- Timed background tasks
- Implement domain events
- Implement background tasks in microservices with IHostedService and the BackgroundService class
- Background tasks with hosted services in ASP.NET Core
- .NET Generic Host in ASP.NET Core
- Application Insights for Worker Service applications (non-HTTP applications)
Alternative to Time Hosted
Instead of using a Timer
its also acceptable to just use a loop that exits based on a CancellationToken
, here its more common to then just use the BackgroundService
base class and only override ExecuteAsync
1 | public class Worker : BackgroundService |
TimedHostedService.Worker.Domain
Event Broker (CartEvents)
This orchestrates the flow and doesnt have to be called a Broker
could be Processor
or anything that makes sense to your team. This will be called by DoWork in TimedHostedService
and only needs one method ProcessAsync
1 | namespace TimedHostedService.Worker.Domain.CartEvents |
Event Feed Service (Services)
This will create the DTO entities based on the JSON events feed, tools like json2csharp.com can help quickly generate these classes.
- HttpEventFeedService implementation of IEventFeedService. I used the
Http
prefix as these events could be fetched by other implementations such as local config file.
1 | namespace TimedHostedService.Worker.Domain.Services |
The domain events are used to explicitly implement side effects across multiple aggregates. In C#, a domain event is simply a data-holding structure or class, like a DTO.
Cart Mapper (CartEvents/Mappers)
We will need to map the EventDto to the domain event CartEvent
1 | namespace TimedHostedService.Worker.Domain.CartEvents.Mappers |
Our EventType‘s are based on the feed.
1 | public enum EventType |
Event Handler (CartEvents/Events/Handlers)
To separate out the concearns we have handlers for each event type.
- CartCreateEventHandler implementation of IEventHandler.
- EmailOnCartCreateEventHandler implementation of ICartMapper (this is just an example of the extensibility, it doesnt actually send emails - but it could)
1 | namespace TimedHostedService.Worker.Domain.CartEvents.Handlers |
TimedHostedService.Worker.Infrastructure
This has Repositories for persistant storage of data and the interfaces are defined in TimedHostedService.Worker.Domain/Interfaces
ICartItemRepository
and ICartRepository
are very simliar.
- ICartRepository implementation of ICartRepository.
- CartItemRepository implementation of ICartItemRepository.
1 | namespace TimedHostedService.Worker.Domain.Interfaces |
IWatermarkRepository
is used to keep track of the watermark in a local .ini file.
1 | namespace TimedHostedService.Worker.Domain.Interfaces |
At the start of each ProcessAsync
in the EventBroker the watermark is read from the ini file.
After each event is dispatched and awaited the watermark is updated with +1
. This implementation of the watermark would NOT scale well if the hosted service was with muiltiple instances but this is fine for a POC.
Thats it, the hosted service would continue to poll for new events and handle them. When new events are needed they can be implemented with IEventHandler and injected as part of the collection using dependency injection.