Skip to content

Commit

Permalink
Basics of the Order Saga Sample
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy D. Miller authored and Jeremy D. Miller committed Jul 8, 2022
1 parent 77bb561 commit b1c00a2
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 9 deletions.
18 changes: 18 additions & 0 deletions Jasper.sln
Expand Up @@ -107,6 +107,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MartenAndRabbitMessages", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleTests", "src\Samples\SampleTests\SampleTests\SampleTests.csproj", "{AB0C2FAA-BA47-496B-8BCB-6EADE454EBBF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OrderSaga", "OrderSaga", "{2A521169-49F9-40CE-B42C-2901801A0172}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderSagaSample", "src\Samples\OrderSagaSample\OrderSagaSample.csproj", "{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -633,6 +637,18 @@ Global
{AB0C2FAA-BA47-496B-8BCB-6EADE454EBBF}.Release|x64.Build.0 = Release|Any CPU
{AB0C2FAA-BA47-496B-8BCB-6EADE454EBBF}.Release|x86.ActiveCfg = Release|Any CPU
{AB0C2FAA-BA47-496B-8BCB-6EADE454EBBF}.Release|x86.Build.0 = Release|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Debug|x64.ActiveCfg = Debug|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Debug|x64.Build.0 = Debug|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Debug|x86.ActiveCfg = Debug|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Debug|x86.Build.0 = Debug|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Release|x64.ActiveCfg = Release|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Release|x64.Build.0 = Release|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Release|x86.ActiveCfg = Release|Any CPU
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -681,6 +697,8 @@ Global
{CE658168-9E0A-4794-8235-32E86A22A344} = {0C76A2E1-0D73-4145-AF40-C7EC64698447}
{BB3D154A-90C7-44F2-88E6-D0C36FC24B43} = {704BCD81-BE9A-48DB-9D83-908155A68ACB}
{AB0C2FAA-BA47-496B-8BCB-6EADE454EBBF} = {0C76A2E1-0D73-4145-AF40-C7EC64698447}
{2A521169-49F9-40CE-B42C-2901801A0172} = {0C76A2E1-0D73-4145-AF40-C7EC64698447}
{AEBBF0D3-46C0-4F6F-9F89-472D81061EF4} = {2A521169-49F9-40CE-B42C-2901801A0172}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D92D723F-44EC-4C1E-AAC3-C162FCEAAA08}
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Expand Up @@ -32,7 +32,7 @@ services:
pgadmin4:
image: dpage/pgadmin4:latest
ports:
- 80:80
- 81:80
environment:
- PGADMIN_DEFAULT_EMAIL=user@domain.com
- PGADMIN_DEFAULT_PASSWORD=SuperSecret
24 changes: 24 additions & 0 deletions src/Jasper/Persistence/Sagas/DisregardIfStateDoesNotExistFrame.cs
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using LamarCodeGeneration;
using LamarCodeGeneration.Frames;
using LamarCodeGeneration.Model;

namespace Jasper.Persistence.Sagas;

public class DisregardIfStateDoesNotExistFrame : SyncFrame
{
private readonly Variable _saga;

public DisregardIfStateDoesNotExistFrame(Variable saga)
{
_saga = saga;
uses.Add(_saga);
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write($"if ({_saga.Usage} == null) return;");
Next?.GenerateCode(method, writer);
}
}
20 changes: 14 additions & 6 deletions src/Jasper/Persistence/Sagas/SagaChain.cs
Expand Up @@ -47,7 +47,7 @@ internal static MemberInfo DetermineSagaIdMember(Type messageType)
var members = messageType.GetFields().OfType<MemberInfo>().Concat(messageType.GetProperties());
return members.FirstOrDefault(x => x.HasAttribute<SagaIdentityAttribute>())
?? members.FirstOrDefault(x => x.Name == SagaIdMemberName) ??
members.FirstOrDefault(x => x.Name == "Id");
members.FirstOrDefault(x => x.Name.EqualsIgnoreCase("Id"));
}

public MemberInfo? SagaIdMember { get; set; }
Expand Down Expand Up @@ -100,11 +100,19 @@ private void generateCodeForMaybeExisting(IContainer container, ISagaPersistence
var saga = load.Creates.Single();
Postprocessors.Add(load);

var startingFrames = DetermineSagaDoesNotExistSteps(sagaId, saga, frameProvider, container).ToArray();
var existingFrames = DetermineSagaExistsSteps(sagaId, saga, frameProvider, container).ToArray();
var ifNullBlock = new IfNullGuard(saga, startingFrames,
existingFrames);
Postprocessors.Add(ifNullBlock);
if (saga.VariableType.CanBeCastTo<TimeoutMessage>())
{
Postprocessors.Add(new DisregardIfStateDoesNotExistFrame(saga));
}
else
{
var startingFrames = DetermineSagaDoesNotExistSteps(sagaId, saga, frameProvider, container).ToArray();
var existingFrames = DetermineSagaExistsSteps(sagaId, saga, frameProvider, container).ToArray();
var ifNullBlock = new IfNullGuard(saga, startingFrames,
existingFrames);

Postprocessors.Add(ifNullBlock);
}
}

private void generateForOnlyStartingSaga(IContainer container, ISagaPersistenceFrameProvider frameProvider)
Expand Down
1 change: 0 additions & 1 deletion src/Samples/CommandBus/Program.cs
Expand Up @@ -4,7 +4,6 @@
using Jasper.ErrorHandling;
using Jasper.Persistence.Marten;
using Marten;
using Microsoft.AspNetCore.Mvc;
using Npgsql;
using Oakton;
using Oakton.Resources;
Expand Down
40 changes: 40 additions & 0 deletions src/Samples/OrderSagaSample/OrderSaga.cs
@@ -0,0 +1,40 @@
using Baseline.Dates;
using Jasper;

namespace OrderSagaSample;

public record StartOrder(string Id);

public record CompleteOrder(string Id);

public record OrderTimeout(string Id) : TimeoutMessage(1.Minutes());

public class Order : Saga
{
public string? Id { get; set; }

public OrderTimeout Start(StartOrder order, ILogger<Order> logger)
{
Id = order.Id; // defining the Saga Id.

logger.LogInformation("Got a new order with id {Id}", order.Id);
// creating a timeout message for the saga
return new OrderTimeout(order.Id);
}

public void Handle(CompleteOrder complete, ILogger<Order> logger)
{
logger.LogInformation("Completing order {Id}", complete.Id);

// That's it, we're done. Delete the saga state after the message is done.
MarkCompleted();
}

public void Handle(OrderTimeout timeout, ILogger<Order> logger)
{
logger.LogInformation("Applying timeout to order {Id}", timeout.Id);

// That's it, we're done. Delete the saga state after the message is done.
MarkCompleted();
}
}
15 changes: 15 additions & 0 deletions src/Samples/OrderSagaSample/OrderSagaSample.csproj
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>10</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Jasper.Persistence.Marten\Jasper.Persistence.Marten.csproj" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
</ItemGroup>

</Project>
48 changes: 48 additions & 0 deletions src/Samples/OrderSagaSample/Program.cs
@@ -0,0 +1,48 @@
using Jasper;
using Jasper.Persistence.Marten;
using Marten;
using Oakton;
using Oakton.Resources;
using OrderSagaSample;

var builder = WebApplication.CreateBuilder(args);

// Not 100% necessary, but enables some extra command line diagnostics
builder.Host.ApplyOaktonExtensions();

// Adding Marten
builder.Services.AddMarten(opts =>
{
var connectionString = builder.Configuration.GetConnectionString("Marten");
opts.Connection(connectionString);
opts.DatabaseSchemaName = "orders";
})

// Adding the Jasper integration for Marten.
.IntegrateWithJasper();


builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Do all necessary database setup on startup
builder.Services.AddResourceSetupOnStartup();

// The defaults are good enough here
builder.Host.UseJasper();

var app = builder.Build();

// Just delegating to Jasper's local command bus for all
app.MapPost("/start", (StartOrder start, ICommandBus bus) => bus.InvokeAsync(start));
app.MapPost("/complete", (StartOrder start, ICommandBus bus) => bus.InvokeAsync(start));
app.MapGet("/all", (IQuerySession session) => session.Query<Order>().ToListAsync());

app.UseSwagger();
app.UseSwaggerUI();

return await app.RunOaktonCommands(args);




13 changes: 13 additions & 0 deletions src/Samples/OrderSagaSample/Properties/launchSettings.json
@@ -0,0 +1,13 @@
{
"profiles": {
"OrderSagaSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5252",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
10 changes: 10 additions & 0 deletions src/Samples/OrderSagaSample/README.md
@@ -0,0 +1,10 @@
# Order Saga Sample


## TODO

-- make all local endpoints be durable
-- give up on Alba specs
-- test for TimeoutMessage on sagas that do not exist
-- streamline Program file again
-- fix transaction issue in DurabilityAgent
8 changes: 8 additions & 0 deletions src/Samples/OrderSagaSample/appsettings.Development.json
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
12 changes: 12 additions & 0 deletions src/Samples/OrderSagaSample/appsettings.json
@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Marten": "Host=localhost;Port=5433;Database=postgres;Username=postgres;password=postgres"
}
}
2 changes: 1 addition & 1 deletion src/Samples/SampleTests/SampleTests/CommandBusTests.cs
Expand Up @@ -15,7 +15,7 @@ public class CommandBusTests
public async Task can_post_without_errors()
{
OaktonEnvironment.AutoStartHost = true;
using var host = await AlbaHost.For<global::Program>(x => { });
using var host = await AlbaHost.For<Program>(x => { });

var store = host.Services.GetRequiredService<IDocumentStore>();
var reservation = new Reservation();
Expand Down
2 changes: 2 additions & 0 deletions src/Samples/SampleTests/SampleTests/SampleTests.csproj
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Alba" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Shouldly" Version="4.0.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />

Expand All @@ -28,6 +29,7 @@

<ItemGroup>
<ProjectReference Include="..\..\CommandBus\CommandBus.csproj" />
<ProjectReference Include="..\..\OrderSagaSample\OrderSagaSample.csproj" />
</ItemGroup>

</Project>

0 comments on commit b1c00a2

Please sign in to comment.