Skip to content

Instantly share code, notes, and snippets.

@djeikyb
Last active August 30, 2021 22:40
Show Gist options
  • Save djeikyb/bacd11762366561cd3a951d1c8d91783 to your computer and use it in GitHub Desktop.
Save djeikyb/bacd11762366561cd3a951d1c8d91783 to your computer and use it in GitHub Desktop.
Demonstrates how a single consumer could be used to apply domain logic to many different models that can be described by a common interface.
using System.Threading.Tasks;
using MassTransit;
using MassTransit.Mediator;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;
namespace Trolley
{
/// <summary>
/// Can encapsulate business logic for a process that applies to many types
/// of IFood. Implementation specific behaviours can be accessed via a type
/// specific gateway.
/// </summary>
/// <typeparam name="T"></typeparam>
class MyConsumer<T> : IConsumer<MyCommand<T>>
where T : IFood
{
private readonly IGateway<T> _gateway;
public MyConsumer(IGateway<T> gateway)
{
_gateway = gateway;
}
public Task Consume(ConsumeContext<MyCommand<T>> context)
{
MyCommand<T>? cmd = context.Message;
T food = _gateway.OpenCambro();
// Helps visually match which command was handled by which consumer.
// Does not affect the mechanics of this pubsub dispatch pattern.
// And it's nice to have at least one property on the command, like
// a real command would likely have.
var foodEnum = cmd.Food;
Log.Information($"{GetType()} consumer invoked with {foodEnum}. {food.Moji}");
return Task.CompletedTask;
}
}
class SpaghattaNadleGateway : IGateway<SpaghattaNadle>
{
public SpaghattaNadle OpenCambro()
{
return new SpaghattaNadle();
}
}
class AvacadaGateway : IGateway<Avacada>
{
public Avacada OpenCambro()
{
return new Avacada();
}
}
/// <typeparam name="T">
/// Used by MassTransit for dispatch. T here must match T in the consumer.
/// </typeparam>
// ReSharper disable once UnusedTypeParameter
class MyCommand<T>
where T : IFood
{
public Food Food { get; init; }
}
interface IFood
{
public Food FoodKind { get; }
public string Moji { get; }
/// <summary>
/// While convenient for this demo, I expect any non-trivial application
/// of this template will need a more complex command factory. I don't
/// expect such a factory to live in this interface.
/// </summary>
public static MyCommand<T> Command<T>() where T : IFood, new()
{
return new MyCommand<T> { Food = new T().FoodKind };
}
}
class Program
{
static async Task Main()
{
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(theme: ConsoleTheme.None)
.CreateLogger();
var host = Host.CreateDefaultBuilder()
.UseSerilog()
.ConfigureServices((_, services) =>
{
services.AddSingleton<IGateway<Avacada>, AvacadaGateway>();
services.AddSingleton<IGateway<SpaghattaNadle>, SpaghattaNadleGateway>();
services.AddMediator(cfg =>
{
cfg.AddConsumer<MyConsumer<Avacada>>();
cfg.AddConsumer<MyConsumer<SpaghattaNadle>>();
});
})
.Build();
Log.Information("Hello World!");
var mediator = host.Services.GetRequiredService<IMediator>();
await mediator.Publish(IFood.Command<Avacada>());
await mediator.Publish(IFood.Command<SpaghattaNadle>());
}
}
class Avacada : IFood
{
public Food FoodKind => Food.Avacada;
public string Moji => "🥑";
}
class SpaghattaNadle : IFood
{
public Food FoodKind => Food.SpaghattaNadle;
public string Moji => "🐍";
}
enum Food
{
Avacada,
SpaghattaNadle,
}
interface IGateway<out T>
where T : IFood
{
T OpenCambro();
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MassTransit" Version="7.2.2"/>
<PackageReference Include="MassTransit.Extensions.DependencyInjection" Version="7.2.2"/>
<PackageReference Include="MassTransit.AspNetCore" Version="7.2.2"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0"/>
<PackageReference Include="Serilog" Version="2.10.0"/>
<PackageReference Include="Serilog.Extensions.Hosting" Version="4.1.2"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0"/>
</ItemGroup>
</Project>
@djeikyb
Copy link
Author

djeikyb commented Aug 27, 2021

@djeikyb
Copy link
Author

djeikyb commented Aug 30, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment