Decouple systems with ASP.NET Core Hosted Service

The story of Hosted Service and Channel to build decoupled systems.

I have been working on a project for around two and a half years. The high architecture is the Event Sourcing-Like. I do not dare to claim it Event Sourcing because it will lead to many debates.

Here is the overview. There are 2 data stores. The primary one is an SQL database storing all events. This is the fact, the truth of the system. The second one is a Cosmos database. This stores the projected views from the events. So the writes go to the Event Store. The reads go to the Cosmos store.

Architecture

The write side composes of Application Services. The read side composes of Projections. We have to send events to the Projections to project them and store the normalized views in Cosmos. The communication has to be asynchronous.

The normal reaction is some kind of a queue. However, we do not want to go that far if we can handle everything inside a single Web Application. Our application is not at StackOverflow scale so a single Web Application instance should be enough. There is a lot of data for sure, but the request rates are reasonable.

Projections as Background Service

The Projections must be run as a background service alongside with the WebApi. With the ASP.NET Core, it is fairly easy with Hosted Service. All good stuffs are documented by Microsoft. Go and check it out. Good stuff! Guaranteed!

We also use Hosted Service for other purposes such as Deleting Files.

Ok, we have the WebApi (the application services) and a background service, how are they communicating to each other?

Channel

It is well-documented, well-explained here DevBlog Channel, or a wonderful blog from Steve Gordon. These links cover everything you need to know about Channel.

To solve the communication issue, we use Channel to create a channel from Application Services to Projections. It is a multi-writers, single-reader channel. If we need a two-way ticket, another channel is created. We also have it for the scenario where the Application Services have to wait for the Projections.

It works beautifully.

Code

So it is time to see some code. All the code is on GitHub

I started a new project with the .NET 6.0 (the latest one). It is cool to use the latest even thought I do not use any special feature in the project.

Let start with a simple stupid API

[ApiController]
[Route("[controller]")]
public class CountersController : ControllerBase
{
    private readonly CountersService _service;

    public CountersController(CountersService service)
    {
        _service = service;
    }

    [HttpPost]
    [Route("/increase")]
    public async Task<IActionResult> Increase()
    {
        await _service.IncreaseCounter();
        return Ok("Have a good day!");
    }
}

The API exposes an endpoint that will make some changes to the system. The controller then dispatches the actual work to a service CountersService. This approach keeps the controller simple, stupid, and clean.

The CountersService handles the logic and finally publishes events.

public class CountersService
{
    private readonly EventChannel _eventChannel;

    public CountersService(EventChannel eventChannel)
    {
        _eventChannel = eventChannel;
    }
    public  Task IncreaseCounter()
    {
        // Execute whatever the business logic here.
        // And finally, write an event package into the event channel.
        // Imagine there is an actual framework here which will publish events.
        // Those events are then packaged into EventPackage and dispatch
        return _eventChannel.Write(new EventPackage
        {
            EventName = "CounterIncreased"
        });
    }
}

That is the end of a service. It does a bunch of stuff and finally publish events (EventPackage).

Who is going to handle those package asynchronously? The Projection Service

public class ProjectionBackgroundService : BackgroundService
{
    private readonly ILogger<ProjectionBackgroundService> _logger;
    private readonly EventChannel _eventChannel;

    public ProjectionBackgroundService(
        ILogger<ProjectionBackgroundService> logger,
        EventChannel eventChannel)
    {
        _logger = logger;
        _eventChannel = eventChannel;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Keep running until asked to stop
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await foreach (var package in _eventChannel.ReadAllAsync(stoppingToken))
                {
                    // Include the logic to proceed a package here.
                    // A package typical contains the actual event (or events) that the projection needs to project
                    _logger.LogInformation($"Package received: {package.EventName}");
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Bad thing happened");
            }
        }
    }
}

It picks up event packages from EventChannel one by one and proceeds.

The final piece that connects them is the EventChannel

public class EventChannel
{
    private readonly Channel<EventPackage> _channel;

    public EventChannel()
    {
        var options = new BoundedChannelOptions(10_000)
        {
            SingleReader = true
        };
        _channel = Channel.CreateBounded<EventPackage>(options);
    }

    public IAsyncEnumerable<EventPackage> ReadAllAsync(CancellationToken cancellationToken)
        => _channel.Reader.ReadAllAsync(cancellationToken);

    public async Task Write(EventPackage eventPackage)
    {
        if (await _channel.Writer.WaitToWriteAsync())
        {
            await _channel.Writer.WriteAsync(eventPackage);
        }
    }
}

public class EventPackage
{
    public string EventName { get; init; }
}

And finally, register those components with ServiceCollection (DI framework in .NET Core)

public static class ServiceCollectionExtensions
{
    public static void RegisterComponents(this IServiceCollection services)
    {
        services.AddHostedService<ProjectionBackgroundService>();
        services.AddScoped<CountersService>();

        services.AddSingleton<EventChannel>();
    }
}

Once run, you should see this in the console

And that is it. I have a decouple architecture that will work.

You were nervous, Daddy!

"Lily, I have had a problem communicating with people recently", I started with my 9 years old daughter.

What’s that, Daddy?

I had problems communicating with people. I have not done a good job at expressing myself.

You were nervous, Daddy!

Oh, really? I immediately denied that statement but I did not tell her. In a second after that thought, a flash of thought came to mind. Maybe she was right. I accepted it.

I were nervous, aggressive might be. I denied the word nervous because it sounds weak. Aggressive sounds strong and cool.

I were nervous. Period.

Thank you, Lily.

Small wins everyday

Success or failure does not happen overnight. The event itself happens overnight or in a matter of second. Every step counts. Every action counts. A person cannot suddenly become smarter, stronger, … It is a journey accumulating every day.

I started to tweak my habits, routines to accumulate small victories everyday. For the last 4 months, I have decided to wake up every day at 5AM and started training (mostly running) with MAF method. The important part is EVERY DAY. I removed the concept of WEEKENDs completely from my mind. Since then, my mind is free from struggling with the weekends.

Regardless of what will show up in the day, I have archived a few small wins already.

By being disciplined to wake up and train at 5AM, it is a huge win (not small at all).

By exercising with MAF method, my energy is high, I feel good, my body is stronger and stronger, I am READY to experience the day. Every day is a gift.

Here is a brief of my morning routine

  1. Wake up at 5AM
  2. Brush my teeth, face, … sanity, prepare my coffee till 5:20AM
  3. Shoes on and start exercising
  4. For the first 10 minutes in the warm-up, I practice English with ELSA app
  5. Run for an hour
  6. Back home, meditate for 5-10 minutes
  7. Clean my house. So my family wakes up with a clean, shining floor
  8. Prepare the day

Some days I missed, but just a few.

The Neglect in the Technical Details and Why It Matters

In software development organizations, what are the common things shared in the discussion of managers? In Agile/Scrum conferences, what are they discussing? Processes, people management, autonomy, … but rarely mentioned technical details.

In one Agile conference, they talked about adapt to changes. I asked: "But how?". A hole bunch of Agile theory came out. None of them actually solved the problem. None of them talked about architecture, the design that allows the team to adapt changes quickly and less expensive.

I did not understand why back then. I just did not like the answer and the way they understood and presented the Agile. It might work for them.

"Clients change their mind often"–it all started from there. If everyone knows what they want and do not change their mind, there is no problem, no job in the world. The software developer job is less interesting, less challenging. I heard they said about "educate clients", "educate other parties". Oh really? How does it work? How many percent of success in that?

The way I see it is that we shift the blame to the clients. We shift our focus to the things we have so little control over. And we ignore the most important part: Build the software, write the code.

Put aside all the contractual agreement, the cost, the time, change requests are eventually changing the code to adapt the new requests.

"How easy is it to change?" – That should be the most important question, IMO.

Most of the time we deal with the problems we created. Why? Because the bad architecture, it is extremely hard to change something in the code.

MVP, Minimum Viable Product, sounds cool than dangerous without careful design and architecture. How hard is it to add new feature after the first MVP? Is it possible to append or rewrite? It all depends on how well the MVP is defined, scope and implemented.

Eventually, regardless of methodologies, what matters most is the code. It is the fundamental block of a software. I really do not want to ignore it in my discussions.

80/20 Rule in Life

It is commonly known as Pareto principle. To me, it reflects the beauty of life—the imbalance—in every aspect. The rule gives a relative concrete number of how imbalance it is. The rule is straightforward and simple. However, to take advantage of the rule, one needs to embed it into the mind, into every day thinking.

If you do the exercise everyday, you will be healthier. Is that true? Not always. Instead this question might be a better choice: What are the 20% of exercise that contributes to 80% health improvement?

I knew this rule when I was in the university but did not truly understand it and how it could help me. Until recently, I read the book 80/20 Principle by Richard Koch, I got it. Well, It might be I am getting older. So I started to reflect on my life and practice it. I want to embed it into my unconscious mind.

Life Control and Decision

Most of the things are out of my control, the weather, the traffic, the time, other people feeling, other people judgements, … and a million more. There is a few things that I am in charge of. Well, it is not 20%, maybe just 0.2%. What are they? my body, my mind, my actions and reactions, my time. In each area, there are 20% of things that matter most, that contribute to the 80% of the outcome.

Find them and practice.

Health

Many only care about it when being sick, feeling tired. Looking suggestions from the internet, from books, there is an exhausting long list of things you should do. They are useless to me. Life is imbalance so to health. I still want to do things that might not be good for my body but I like it. That is life and I want to enjoy it.

However, thinking in 80/20 helps me to find the 20% activities that will contribute to 80% of my health improvement. The rest 80% will cause 20% damage. It is an acceptable imbalance.

Here are some I found recently and practice.

  1. Reduce junk food
  2. No sugar. There is sugar in cooked food. But I do not actively put sugar in my food, drink. I go with original
  3. Exercise with MAF methodology. This one is the best thing I found this year. I have practiced it for 3 months. Wonderful! I like the result
  4. Sleep. Increase the amount and quality of sleep. Eye opening from reading the book "Why We Sleep"-Matthew Walker. I suggest everyone should read it. I do not try to have 7-9 hours per night but try to more than 6h per night.

Time

The most scared resource in the universe, 24h a day is what you are given, same amount for everyone. I will refuse the activities that have 20% impact but consume 80% of my time. It is simply too expensive that I cannot afford. I like drinking beer. I used to have heavy drinking in the weekend. The joy was less than 20%, only benefit at the drinking time, but the cost was way too high, the hangover the next days.

This part is really hard. Practice makes perfect.

Software Design and Architecture

The rule is perfect for software design and architecture. Start with asking these questions

  1. What are the 20% that contribute to the 80% success of a project?
  2. What are the 20% that contribute to the 80% success of this module, feature?

There are millions of pieces, lines of code, … which ones are the 20%?

Focus on them and relax a bit on the rest. Everything is a trade-off, so spend time and energy on the right ones.

Team Management and Assignment

There are certain things that you MUST do. That is the 20%.
Find out that 20% and do it yourself, the rest is delegated to others.
20% of members contribute to the 80% of the team success.

And What Else

The list goes on. It is interesting to look at life in this angle.

Where is the 20%?