Skip to content

Commit

Permalink
Merge pull request #53 from atc-net/getting-started-docs
Browse files Browse the repository at this point in the history
Sample project for Getting Started
  • Loading branch information
LarsSkovslund authored Dec 5, 2024
2 parents 4ed3fe1 + 22fabdd commit 3e09337
Show file tree
Hide file tree
Showing 17 changed files with 410 additions and 115 deletions.
80 changes: 0 additions & 80 deletions sample/.editorconfig

This file was deleted.

31 changes: 31 additions & 0 deletions sample/GettingStarted/Commands/CreateCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

public record CreateCommand(string Id, string Name, string Address)
: CommandBase<SampleEventStreamId>(new SampleEventStreamId(Id));

public class CreateCommandHandler :
ICommandHandler<CreateCommand>,
IConsumeEvent<CreateCommand>
{
private bool created;

public void Consume(CreateCommand evt, EventMetadata metadata)
{
this.created = true;
}

public ValueTask ExecuteAsync(
CreateCommand command,
ICommandContext context,
CancellationToken cancellationToken)
{
if (!created)
{
context.AddEvent(new AddedEvent(command.Name, command.Address));
}

return ValueTask.CompletedTask;
}
}
44 changes: 44 additions & 0 deletions sample/GettingStarted/Commands/DeleteCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

public record DeleteCommand(string Id, string Reason)
: CommandBase<SampleEventStreamId>(new SampleEventStreamId(Id));

public class DeleteCommandHandler :
ICommandHandler<DeleteCommand>,
IConsumeEvent<AddedEvent>,
IConsumeEvent<DeletedEvent>
{
private bool created;
private bool deleted;

public void Consume(AddedEvent evt, EventMetadata metadata)
{
this.created = true;
}

public void Consume(DeletedEvent evt, EventMetadata metadata)
{
this.deleted = true;
}

public ValueTask ExecuteAsync(
DeleteCommand command,
ICommandContext context,
CancellationToken cancellationToken)
{
if (!created)
{
throw new InvalidOperationException("Cannot delete non-existing entity.");
}

if (deleted)
{
throw new InvalidOperationException("Already deleted.");
}

context.AddEvent(new DeletedEvent(command.Reason));
return ValueTask.CompletedTask;
}
}
44 changes: 44 additions & 0 deletions sample/GettingStarted/Commands/UpdateNameCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

public record UpdateNameCommand(string Id, string Name)
: CommandBase<SampleEventStreamId>(new SampleEventStreamId(Id));

public class UpdateNameCommandHandler :
ICommandHandler<UpdateNameCommand>,
IConsumeEvent<AddedEvent>,
IConsumeEvent<NameChangedEvent>
{
private bool created;
private string? currentName;

public void Consume(AddedEvent evt, EventMetadata metadata)
{
created = true;
currentName = evt.Name;
}

public void Consume(NameChangedEvent evt, EventMetadata metadata)
{
currentName = evt.NewName;
}

public ValueTask ExecuteAsync(
UpdateNameCommand command,
ICommandContext context,
CancellationToken cancellationToken)
{
if (!created)
{
throw new InvalidOperationException("Cannot change name on non-existing entity.");
}

if (currentName != command.Name)
{
context.AddEvent(new NameChangedEvent(currentName!, command.Name));
}

return ValueTask.CompletedTask;
}
}
44 changes: 44 additions & 0 deletions sample/GettingStarted/ConsoleHostedService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Atc.Cosmos.EventStore.Cqrs;
using Microsoft.Extensions.Hosting;

namespace GettingStarted;

public class ConsoleHostedService(ICommandProcessorFactory commandProcessorFactory) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
var id = Guid.NewGuid().ToString("N");

await commandProcessorFactory
.Create<CreateCommand>()
.ExecuteAsync(
new CreateCommand(id, "First", "Address 1"),
cancellationToken);

await commandProcessorFactory
.Create<UpdateNameCommand>()
.ExecuteAsync(
new UpdateNameCommand(id, "Second"),
cancellationToken);

await commandProcessorFactory
.Create<UpdateNameCommand>()
.ExecuteAsync(
new UpdateNameCommand(id, "Third"),
cancellationToken);

await commandProcessorFactory
.Create<DeleteCommand>()
.ExecuteAsync(
new DeleteCommand(id, "Deleted"),
cancellationToken);

await commandProcessorFactory
.Create<DeleteCommand>()
.ExecuteAsync(
new DeleteCommand(id, "Deleted"),
cancellationToken);
}

public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
6 changes: 6 additions & 0 deletions sample/GettingStarted/Events/AddedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

[StreamEvent("added-event:v1")]
public record AddedEvent(string Name, string Address);
6 changes: 6 additions & 0 deletions sample/GettingStarted/Events/AddressChangedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

[StreamEvent("address-changed-event:v1")]
public record AddressChangedEvent(string OldAddress, string NewAddress);
6 changes: 6 additions & 0 deletions sample/GettingStarted/Events/DeletedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

[StreamEvent("deleted-event:v1")]
public record DeletedEvent(string Reason);
6 changes: 6 additions & 0 deletions sample/GettingStarted/Events/NameChangedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Atc.Cosmos.EventStore.Cqrs;

namespace GettingStarted;

[StreamEvent("name-changed-event:v1")]
public record NameChangedEvent(string OldName, string NewName);
17 changes: 17 additions & 0 deletions sample/GettingStarted/GettingStarted.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Atc.Cosmos" Version="1.1.40" />
<PackageReference Include="Atc.Cosmos.EventStore.Cqrs" Version="1.12.6" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>

</Project>
62 changes: 62 additions & 0 deletions sample/GettingStarted/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Atc.Cosmos;
using Atc.Cosmos.EventStore;
using GettingStarted;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Atc.Cosmos.Internal;
using GettingStarted.Storage;
using Microsoft.Extensions.Options;

void ConfigureServices(IServiceCollection services)
{
services.ConfigureOptions<ConfigureCosmosOptions>();
services.ConfigureCosmos(builder =>
{
builder.AddContainer<ContainerInitializer, SampleReadModel>(ContainerInitializer.Name);
builder.UseHostedService();
});

services.ConfigureOptions<ConfigureEventStoreOptions>();
services.AddEventStore(
builder =>
{
builder.UseCosmosDb();
builder.UseEvents(catalogBuilder => catalogBuilder.FromAssembly<AddedEvent>());
builder.UseCQRS(
c =>
{
c.AddInitialization(
4000,
serviceProvider => serviceProvider
.GetRequiredService<ICosmosInitializer>()
.InitializeAsync(CancellationToken.None));

c.AddCommandsFromAssembly<CreateCommand>();
c.AddProjectionJob<SampleProjection>(nameof(SampleProjection));
});
});

services.AddHostedService<ConsoleHostedService>();
}

await Host.CreateDefaultBuilder()
.ConfigureServices(ConfigureServices)
.RunConsoleAsync();

public class ConfigureCosmosOptions : IConfigureOptions<CosmosOptions>
{
public void Configure(CosmosOptions options)
{
options.UseCosmosEmulator();
options.DatabaseName = "CQRS";
}
}

public class ConfigureEventStoreOptions : IConfigureOptions<EventStoreClientOptions>
{
public void Configure(EventStoreClientOptions options)
{
options.UseCosmosEmulator();
options.EventStoreDatabaseId = "CQRS";
}
}
25 changes: 25 additions & 0 deletions sample/GettingStarted/Projections/ContainerInitializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Atc.Cosmos;
using Microsoft.Azure.Cosmos;

namespace GettingStarted.Storage;

public class ContainerInitializer : ICosmosContainerInitializer
{
public const string Name = "read-models";

public Task InitializeAsync(Database database, CancellationToken cancellationToken)
{
var options = new ContainerProperties
{
IndexingPolicy = new IndexingPolicy
{
Automatic = true,
IndexingMode = IndexingMode.Consistent,
},
PartitionKeyPath = "/pk",
Id = Name,
};

return database.CreateContainerIfNotExistsAsync(options, cancellationToken: cancellationToken);
}
}
Loading

0 comments on commit 3e09337

Please sign in to comment.