Skip to content

Commit

Permalink
first working sample
Browse files Browse the repository at this point in the history
  • Loading branch information
tomohisa committed Aug 15, 2023
1 parent 431b2ba commit b953380
Show file tree
Hide file tree
Showing 24 changed files with 274 additions and 24 deletions.
14 changes: 14 additions & 0 deletions Sekiban.sln
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Convert011To012", "tools\Co
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixed.Test", "test\Mixed.Test\Mixed.Test.csproj", "{630F88BB-1967-4F9A-B6E2-771922069319}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fsCustomer", "samples\fsharp\fsCustomer.fsproj", "{3BDC7B2C-F56C-4EDF-AE50-D2ADCF9538A3}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fsCustomerTest", "fsCustomerTest\fsCustomerTest.fsproj", "{8E69545E-24E1-48D2-A0C5-5219FF2E36D9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -155,6 +159,14 @@ Global
{630F88BB-1967-4F9A-B6E2-771922069319}.Debug|Any CPU.Build.0 = Debug|Any CPU
{630F88BB-1967-4F9A-B6E2-771922069319}.Release|Any CPU.ActiveCfg = Release|Any CPU
{630F88BB-1967-4F9A-B6E2-771922069319}.Release|Any CPU.Build.0 = Release|Any CPU
{3BDC7B2C-F56C-4EDF-AE50-D2ADCF9538A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3BDC7B2C-F56C-4EDF-AE50-D2ADCF9538A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3BDC7B2C-F56C-4EDF-AE50-D2ADCF9538A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3BDC7B2C-F56C-4EDF-AE50-D2ADCF9538A3}.Release|Any CPU.Build.0 = Release|Any CPU
{8E69545E-24E1-48D2-A0C5-5219FF2E36D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E69545E-24E1-48D2-A0C5-5219FF2E36D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E69545E-24E1-48D2-A0C5-5219FF2E36D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E69545E-24E1-48D2-A0C5-5219FF2E36D9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -183,6 +195,8 @@ Global
{091121AC-BDAB-46B3-8A49-A63D0EF6FAA5} = {B85F80AE-6A27-402C-93FA-352D938EDA8D}
{482C14AE-C213-4234-9B11-F0D7E953EEE2} = {9504B5F4-E803-46E3-B110-CBA8196B03D4}
{630F88BB-1967-4F9A-B6E2-771922069319} = {4C34F748-F223-44DD-899D-029F9CEF7780}
{3BDC7B2C-F56C-4EDF-AE50-D2ADCF9538A3} = {B85F80AE-6A27-402C-93FA-352D938EDA8D}
{8E69545E-24E1-48D2-A0C5-5219FF2E36D9} = {4C34F748-F223-44DD-899D-029F9CEF7780}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2DFBE53E-69A7-453A-8D20-72271AE32016}
Expand Down
1 change: 1 addition & 0 deletions fsCustomerTest/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module Program = let [<EntryPoint>] main _ = 0
47 changes: 47 additions & 0 deletions fsCustomerTest/Tests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module fsCustomerTest.Tests

open System.Text.Json
open Sekiban.Core.Shared
open Sekiban.Testing.SingleProjections
open Xunit
open Xunit.Abstractions
open fsCustomer.Dependency
open fsCustomer.Domain

[<Fact>]
let ``My test`` () = Assert.True(true)




type BranchSpec(testOutputHelper: ITestOutputHelper) =
inherit AggregateTest<Branch, FsCustomerDependency>()
member this.TestOutputHelper = testOutputHelper

[<Fact>]
member this.Serialize() =
let serialized = SekibanJsonHelper.Serialize(Branch("Japan"))
this.TestOutputHelper.WriteLine(serialized)
let deserialized = SekibanJsonHelper.Deserialize<Branch>(serialized)
let serializedFromDeserialized = SekibanJsonHelper.Serialize(deserialized)
this.TestOutputHelper.WriteLine(serializedFromDeserialized)
Assert.Equal(serialized, serializedFromDeserialized)

[<Fact>]
member this.SerializeOptionChecking() =
let options: JsonSerializerOptions = JsonSerializerOptions()
// options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
// options.PropertyNameCaseInsensitive = true
let serialized = JsonSerializer.Serialize<Branch>(Branch("Japan"), options)
this.TestOutputHelper.WriteLine(serialized)
let deserialized = JsonSerializer.Deserialize<Branch>(serialized, options)

let serializedFromDeserialized =
JsonSerializer.Serialize<Branch>(deserialized, options)

this.TestOutputHelper.WriteLine(serializedFromDeserialized)
Assert.Equal(serialized, serializedFromDeserialized)

[<Fact>]
member this.CreateAggregate() =
this.WhenCommand(CreateBranch("Japan")).ThenPayloadIs(Branch("Japan"))
34 changes: 34 additions & 0 deletions fsCustomerTest/fsCustomerTest.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>

<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<Compile Include="Tests.fs"/>
<Compile Include="Program.fs"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\samples\fsharp\fsCustomer.fsproj"/>
<ProjectReference Include="..\src\Sekiban.Testing\Sekiban.Testing.csproj"/>
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMajor",
"allowPrerelease": true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public class FeatureCheckDependency : DomainDependencyDefinitionBase
{
public override Assembly GetExecutingAssembly() => Assembly.GetExecutingAssembly();

protected override void Define()
public override void Define()
{
AddAggregate<Branch>()
.AddCommandHandler<CreateBranch, CreateBranch.Handler>()
Expand Down
2 changes: 1 addition & 1 deletion samples/Mixed.Domain/MixedContextDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class MixedContextDependency : DomainDependencyDefinitionBase
{
public override Assembly GetExecutingAssembly() => Assembly.GetExecutingAssembly();

protected override void Define()
public override void Define()
{
AddDependency<ShippingDependency>()
.AddDependency<WarehouseDependency>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class MultiTenantDependency : DomainDependencyDefinitionBase
{

public override Assembly GetExecutingAssembly() => Assembly.GetExecutingAssembly();
protected override void Define()
public override void Define()
{
AddAggregate<ClientPayload>().AddCommandHandler<CreateClient, CreateClient.Handler>().AddAggregateListQuery<ClientListQuery>();
}
Expand Down
2 changes: 1 addition & 1 deletion samples/Shipping.Domain/ShippingDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class ShippingDependency : DomainDependencyDefinitionBase
{
public override Assembly GetExecutingAssembly() => Assembly.GetExecutingAssembly();

protected override void Define()
public override void Define()
{
AddAggregate<Product>()
.AddCommandHandler<CreateProduct, CreateProduct.Handler>()
Expand Down
2 changes: 1 addition & 1 deletion samples/Warehouse.Domain/WarehouseDependency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class WarehouseDependency : DomainDependencyDefinitionBase
{
public override Assembly GetExecutingAssembly() => Assembly.GetExecutingAssembly();

protected override void Define()
public override void Define()
{
AddAggregate<ProductStock>()
.AddCommandHandler<AddProductStock, AddProductStock.Handler>()
Expand Down
24 changes: 24 additions & 0 deletions samples/fsharp/Dependency.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module fsCustomer.Dependency

open System.Reflection
open Sekiban.Core.Dependency
open fsCustomer.Domain


type FsCustomerDependency() =
inherit DomainDependencyDefinitionBase()

override this.Define() =
do
this
.AddAggregate<Branch>()
.AddCommandHandler<CreateBranch, CreateBranchHandler>()
|> ignore

this
.AddAggregate<Client>()
.AddCommandHandler<CreateClient, CreateClientHandler>()
.AddAggregateQuery<ClientEmailExistsQuery>()
|> ignore

override this.GetExecutingAssembly() = Assembly.GetExecutingAssembly()
92 changes: 92 additions & 0 deletions samples/fsharp/Domain.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
module fsCustomer.Domain

open System
open System.Text.Json.Serialization
open FSharp.Control
open Sekiban.Core.Aggregate
open Sekiban.Core.Command
open Sekiban.Core.Events
open Sekiban.Core.Query.QueryModel

type Branch [<JsonConstructor>] (name: string) =
member this.Name = name
interface IAggregatePayload
new() = Branch("")

type CreateBranch(name: string) =
member this.Name = name

interface ICommand<Branch> with
member this.GetAggregateId() = Guid.NewGuid()

type BranchCreated =
{ Name: string }

interface IEventPayload<Branch, Branch, BranchCreated> with
static member OnEvent(aggregatePayload, ev) = Branch(ev.Payload.Name)

type CreateBranchHandler() =
interface ICommandHandler<Branch, CreateBranch> with
member this.HandleCommandAsync(getAggregateState, command) =
taskSeq { yield { Name = command.Name } :> IEventPayloadApplicableTo<Branch> }


type Client(name: string, email: string, branchId: Guid) =
member this.Name = name
member this.Email = email
member this.BranchId = branchId
interface IAggregatePayload
new() = Client("", "", Guid.Empty)

type CreateClient =
{ Name: string
Email: string
BranchId: Guid }

interface ICommand<Client> with
member this.GetAggregateId() = Guid.NewGuid()

type ClientCreated =
{ Name: string
Email: string
BranchId: Guid }

interface IEventPayload<Client, Client, ClientCreated> with
static member OnEvent(aggregatePayload, ev) =
Client(ev.Payload.Name, ev.Payload.Email, ev.Payload.BranchId)

type ClientEmailExistsQueryResponse =
{ Exists: bool }

interface IQueryResponse

type ClientEmailExistsQueryParam =
{ Email: string }

interface IQueryParameter<ClientEmailExistsQueryResponse>



type ClientEmailExistsQuery =
interface IAggregateQuery<Client, ClientEmailExistsQueryParam, ClientEmailExistsQueryResponse> with
member this.HandleFilter(queryParam, list) =
{ Exists = list |> Seq.exists (fun client -> client.Payload.Email = queryParam.Email) }

type CreateClientHandler(queryExecutor: IQueryExecutor) =
member this.QueryExecutor = queryExecutor

interface ICommandHandler<Client, CreateClient> with
member this.HandleCommandAsync(getAggregateState, command) =
taskSeq {
let branchExistsResponse =
this.QueryExecutor.ExecuteAsync({ Email = command.Email })
|> Async.AwaitTask
|> Async.RunSynchronously

if branchExistsResponse.Exists then
yield
{ Name = command.Name
Email = command.Email
BranchId = command.BranchId }
:> IEventPayloadApplicableTo<Client>
}
5 changes: 5 additions & 0 deletions samples/fsharp/Library.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace fsCustomer

module Say =
let hello name =
printfn "Hello %s" name
24 changes: 24 additions & 0 deletions samples/fsharp/fsCustomer.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>default</LangVersion>
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs"/>
<Compile Include="Domain.fs"/>
<Compile Include="Dependency.fs"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Sekiban.Core\Sekiban.Core.csproj"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Control.AsyncSeq" Version="3.2.1"/>
<PackageReference Include="FSharp.Control.TaskSeq" Version="0.3.0"/>
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions src/Sekiban.Core/Command/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public async Task<CommandExecutorResponseWithEvents> ExecCommandWithoutValidatio

public async Task<(CommandExecutorResponse, List<IEvent>)> ExecCommandAsyncTyped<TAggregatePayload, TCommand>(
TCommand command,
List<CallHistory>? callHistories = null) where TAggregatePayload : IAggregatePayloadCommon where TCommand : ICommand<TAggregatePayload>
List<CallHistory>? callHistories = null) where TAggregatePayload : IAggregatePayloadCommon, new() where TCommand : ICommand<TAggregatePayload>
{
var validationResult = command.ValidateProperties().ToList();
if (validationResult.Any())
Expand All @@ -108,7 +108,7 @@ public async Task<CommandExecutorResponseWithEvents> ExecCommandWithoutValidatio

public async Task<(CommandExecutorResponse, List<IEvent>)> ExecCommandWithoutValidationAsyncTyped<TAggregatePayload, TCommand>(
TCommand command,
List<CallHistory>? callHistories = null) where TAggregatePayload : IAggregatePayloadCommon where TCommand : ICommand<TAggregatePayload>
List<CallHistory>? callHistories = null) where TAggregatePayload : IAggregatePayloadCommon, new() where TCommand : ICommand<TAggregatePayload>
{
var rootPartitionKey = command.GetRootPartitionKey();
if (!CommandRootPartitionValidationAttribute.IsValidRootPartitionKey(rootPartitionKey))
Expand Down
2 changes: 1 addition & 1 deletion src/Sekiban.Core/Command/CommandHandlerAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Sekiban.Core.Command;
/// System use command handler adapter.
/// Application Developer does not need to implement this interface
/// </summary>
public sealed class CommandHandlerAdapter<TAggregatePayload, TCommand> where TAggregatePayload : IAggregatePayloadCommon
public sealed class CommandHandlerAdapter<TAggregatePayload, TCommand> where TAggregatePayload : IAggregatePayloadCommon, new()
where TCommand : ICommand<TAggregatePayload>
{
private readonly IAggregateLoader _aggregateLoader;
Expand Down
2 changes: 1 addition & 1 deletion src/Sekiban.Core/Command/ICommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Sekiban.Core.Command;
/// <typeparam name="TAggregatePayload">Target Aggregate</typeparam>
/// <typeparam name="TCommand">Target Command</typeparam>
public interface ICommandHandler<TAggregatePayload, TCommand> : ICommandHandlerCommon<TAggregatePayload, TCommand>
where TAggregatePayload : IAggregatePayloadCommon where TCommand : ICommand<TAggregatePayload>
where TAggregatePayload : IAggregatePayloadCommon, new() where TCommand : ICommand<TAggregatePayload>
{
/// <summary>
/// A Command Handler.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ namespace Sekiban.Core.Command;
/// <typeparam name="TAggregatePayload"></typeparam>
/// <typeparam name="TCommand"></typeparam>
public interface IVersionValidationCommandHandler<TAggregatePayload, TCommand> : ICommandHandler<TAggregatePayload, TCommand>
where TAggregatePayload : IAggregatePayloadCommon where TCommand : IVersionValidationCommand<TAggregatePayload>;
where TAggregatePayload : IAggregatePayloadCommon, new() where TCommand : IVersionValidationCommand<TAggregatePayload>;
8 changes: 4 additions & 4 deletions src/Sekiban.Core/Dependency/AggregateDependencyDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ public class AggregateDependencyDefinition<TAggregatePayload> : IAggregateDepend
/// <summary>
/// Add Command Handler to Aggregate
/// </summary>
/// <typeparam name="TCreateCommand">Target Command</typeparam>
/// <typeparam name="TCommand">Target Command</typeparam>
/// <typeparam name="TCommandHandler">Command Handler for Target Command</typeparam>
/// <returns>Self for method chain</returns>
public AggregateDependencyDefinition<TAggregatePayload> AddCommandHandler<TCreateCommand, TCommandHandler>()
where TCreateCommand : ICommand<TAggregatePayload>, new() where TCommandHandler : ICommandHandlerCommon<TAggregatePayload, TCreateCommand>
public AggregateDependencyDefinition<TAggregatePayload> AddCommandHandler<TCommand, TCommandHandler>()
where TCommand : ICommand<TAggregatePayload> where TCommandHandler : ICommandHandlerCommon<TAggregatePayload, TCommand>
{
SelfCommandTypes = SelfCommandTypes.Add((typeof(ICommandHandlerCommon<TAggregatePayload, TCreateCommand>), typeof(TCommandHandler)));
SelfCommandTypes = SelfCommandTypes.Add((typeof(ICommandHandlerCommon<TAggregatePayload, TCommand>), typeof(TCommandHandler)));
return this;
}
/// <summary>
Expand Down
Loading

0 comments on commit b953380

Please sign in to comment.