diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 25f43e7..7b53e0e 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -5,9 +5,9 @@ name: .NET on: push: - branches: [ "main" ] + branches: [ "main", "develop" ] pull_request: - branches: [ "main" ] + branches: [ "main", "develop" ] jobs: build: diff --git a/Coflo.sln b/Coflo.sln index bda815c..166947b 100644 --- a/Coflo.sln +++ b/Coflo.sln @@ -16,27 +16,17 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Abstractions", "sourc EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "infrastructure", "infrastructure", "{E9952EF9-60A2-441B-BAFB-C285D8ED4761}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Infrastructure.Messaging.RabbitMQ", "source\infrastructure\messaging\Coflo.Infrastructure.Messaging.RabbitMQ\Coflo.Infrastructure.Messaging.RabbitMQ.csproj", "{28F9D028-7AD1-4345-AFCF-13413E5F6250}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "messaging", "messaging", "{63D82DB8-ADE8-4D8E-A1BC-DB9A29ADCACE}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "locking", "locking", "{B57221CE-ABE3-420A-8802-C3FF5FD03EAD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Infrastructure.Locking.Redis", "source\infrastructure\locking\Coflo.Infrastructure.Locking.Redis\Coflo.Infrastructure.Locking.Redis.csproj", "{F7A0F3BB-F8E3-4739-AB71-7704403C8FC8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "persistance", "persistance", "{B4807B40-63D6-4119-ACE5-8E65EBBFFFAD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Infrastructure.Persistance.MySQL", "source\infrastructure\persistance\Coflo.Infrastructure.Persistance.MySQL\Coflo.Infrastructure.Persistance.MySQL.csproj", "{C4FA592B-9A2E-4062-8746-D8684ECB545E}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hosting", "hosting", "{DEAF9660-31F2-4E6B-9A77-93BA08D7D122}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Hosting.Worker", "source\hosting\Coflo.Hosting.Worker\Coflo.Hosting.Worker.csproj", "{60FE92B2-095B-46C3-8FEC-8C2350E92884}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Hosting.Api.Rest", "source\hosting\Coflo.Hosting.Api.Rest\Coflo.Hosting.Api.Rest.csproj", "{AE88171D-FB16-4686-B23E-1C5F75DF7DC2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Hosting.Api.GRPC", "source\hosting\Coflo.Hosting.Api.GRPC\Coflo.Hosting.Api.GRPC.csproj", "{47131B79-A7E2-4A46-B9E8-98DDC0C20FCE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.SDK", "source\core\Coflo.SDK\Coflo.SDK.csproj", "{2569CD34-41EF-42A3-AE63-BE01A0124D44}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Core", "source\core\Coflo.Core\Coflo.Core.csproj", "{2569CD34-41EF-42A3-AE63-BE01A0124D44}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1230DC9F-E226-4466-B0B1-99B52B66232C}" EndProject @@ -44,6 +34,26 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{FA4C5E4B-B EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.SDK.Tests", "tests\core\Coflo.SDK.Tests\Coflo.SDK.Tests.csproj", "{7FE22FFD-CB25-446A-9192-76127270C3E1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Hosting.Orchestrator", "source\hosting\Coflo.Hosting.Orchestrator\Coflo.Hosting.Orchestrator.csproj", "{3009ACF8-F2DC-492C-8E41-7E36AB3A7A6C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Core.Snowflake", "source\hosting\Coflo.Core.Snowflake\Coflo.Core.Snowflake.csproj", "{2FF4D33E-2964-41B3-B0B2-3C9D70616FBA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Core.Snowflake.Tests", "tests\core\Coflo.Core.Snowflake.Tests\Coflo.Core.Snowflake.Tests.csproj", "{75110834-EBD6-450A-9092-B619CE02FEAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "activities", "activities", "{A4E3E7F1-AA46-4934-90BA-598CE381F0AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Activities.Primitives", "source\activities\Coflo.Activities.Primitives\Coflo.Activities.Primitives.csproj", "{CDA31FB1-04BA-4A5D-9282-7EBAB755BE7E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hosting", "hosting", "{3DA27C4E-FE4A-4805-BDB7-61BDFFB9B30F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Hosting.Worker.Tests", "tests\hosting\Coflo.Hosting.Worker.Tests\Coflo.Hosting.Worker.Tests.csproj", "{423ECCA1-DBC1-4947-8BD6-AAF3A8A55B37}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "caching", "caching", "{6F96D4D7-A683-44F1-8BBD-03684B7DF5DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Infrastructure.Caching.Redis", "source\infrastructure\caching\Coflo.Infrastructure.Caching.Redis\Coflo.Infrastructure.Caching.Redis.csproj", "{850C0239-46E5-4B2B-BBE8-954C4E93EDB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Coflo.Infrastructure.Persistence.Cassandra", "source\infrastructure\persistance\Coflo.Infrastructure.Persistence.Cassandra\Coflo.Infrastructure.Persistence.Cassandra.csproj", "{FFE529A5-CF9D-46F6-9682-A23ED32B6648}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,49 +63,38 @@ Global {C46B2D26-B277-4EE0-9497-0473D52EE7A9} = {F514E571-76C3-477F-86F0-3C8CCF512015} {28DE14D7-4F2E-40D9-B847-69866240023D} = {C46B2D26-B277-4EE0-9497-0473D52EE7A9} {E9952EF9-60A2-441B-BAFB-C285D8ED4761} = {F514E571-76C3-477F-86F0-3C8CCF512015} - {63D82DB8-ADE8-4D8E-A1BC-DB9A29ADCACE} = {E9952EF9-60A2-441B-BAFB-C285D8ED4761} - {28F9D028-7AD1-4345-AFCF-13413E5F6250} = {63D82DB8-ADE8-4D8E-A1BC-DB9A29ADCACE} {B57221CE-ABE3-420A-8802-C3FF5FD03EAD} = {E9952EF9-60A2-441B-BAFB-C285D8ED4761} {F7A0F3BB-F8E3-4739-AB71-7704403C8FC8} = {B57221CE-ABE3-420A-8802-C3FF5FD03EAD} {B4807B40-63D6-4119-ACE5-8E65EBBFFFAD} = {E9952EF9-60A2-441B-BAFB-C285D8ED4761} - {C4FA592B-9A2E-4062-8746-D8684ECB545E} = {B4807B40-63D6-4119-ACE5-8E65EBBFFFAD} {DEAF9660-31F2-4E6B-9A77-93BA08D7D122} = {F514E571-76C3-477F-86F0-3C8CCF512015} {60FE92B2-095B-46C3-8FEC-8C2350E92884} = {DEAF9660-31F2-4E6B-9A77-93BA08D7D122} - {AE88171D-FB16-4686-B23E-1C5F75DF7DC2} = {DEAF9660-31F2-4E6B-9A77-93BA08D7D122} - {47131B79-A7E2-4A46-B9E8-98DDC0C20FCE} = {DEAF9660-31F2-4E6B-9A77-93BA08D7D122} {2569CD34-41EF-42A3-AE63-BE01A0124D44} = {C46B2D26-B277-4EE0-9497-0473D52EE7A9} {FA4C5E4B-B42D-4BC5-9F26-FD32CD88F75F} = {1230DC9F-E226-4466-B0B1-99B52B66232C} {7FE22FFD-CB25-446A-9192-76127270C3E1} = {FA4C5E4B-B42D-4BC5-9F26-FD32CD88F75F} + {3009ACF8-F2DC-492C-8E41-7E36AB3A7A6C} = {DEAF9660-31F2-4E6B-9A77-93BA08D7D122} + {2FF4D33E-2964-41B3-B0B2-3C9D70616FBA} = {C46B2D26-B277-4EE0-9497-0473D52EE7A9} + {75110834-EBD6-450A-9092-B619CE02FEAC} = {FA4C5E4B-B42D-4BC5-9F26-FD32CD88F75F} + {A4E3E7F1-AA46-4934-90BA-598CE381F0AA} = {F514E571-76C3-477F-86F0-3C8CCF512015} + {CDA31FB1-04BA-4A5D-9282-7EBAB755BE7E} = {A4E3E7F1-AA46-4934-90BA-598CE381F0AA} + {3DA27C4E-FE4A-4805-BDB7-61BDFFB9B30F} = {1230DC9F-E226-4466-B0B1-99B52B66232C} + {423ECCA1-DBC1-4947-8BD6-AAF3A8A55B37} = {3DA27C4E-FE4A-4805-BDB7-61BDFFB9B30F} + {6F96D4D7-A683-44F1-8BBD-03684B7DF5DD} = {E9952EF9-60A2-441B-BAFB-C285D8ED4761} + {850C0239-46E5-4B2B-BBE8-954C4E93EDB4} = {6F96D4D7-A683-44F1-8BBD-03684B7DF5DD} + {FFE529A5-CF9D-46F6-9682-A23ED32B6648} = {B4807B40-63D6-4119-ACE5-8E65EBBFFFAD} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {28DE14D7-4F2E-40D9-B847-69866240023D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {28DE14D7-4F2E-40D9-B847-69866240023D}.Debug|Any CPU.Build.0 = Debug|Any CPU {28DE14D7-4F2E-40D9-B847-69866240023D}.Release|Any CPU.ActiveCfg = Release|Any CPU {28DE14D7-4F2E-40D9-B847-69866240023D}.Release|Any CPU.Build.0 = Release|Any CPU - {28F9D028-7AD1-4345-AFCF-13413E5F6250}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {28F9D028-7AD1-4345-AFCF-13413E5F6250}.Debug|Any CPU.Build.0 = Debug|Any CPU - {28F9D028-7AD1-4345-AFCF-13413E5F6250}.Release|Any CPU.ActiveCfg = Release|Any CPU - {28F9D028-7AD1-4345-AFCF-13413E5F6250}.Release|Any CPU.Build.0 = Release|Any CPU {F7A0F3BB-F8E3-4739-AB71-7704403C8FC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7A0F3BB-F8E3-4739-AB71-7704403C8FC8}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7A0F3BB-F8E3-4739-AB71-7704403C8FC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7A0F3BB-F8E3-4739-AB71-7704403C8FC8}.Release|Any CPU.Build.0 = Release|Any CPU - {C4FA592B-9A2E-4062-8746-D8684ECB545E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C4FA592B-9A2E-4062-8746-D8684ECB545E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C4FA592B-9A2E-4062-8746-D8684ECB545E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C4FA592B-9A2E-4062-8746-D8684ECB545E}.Release|Any CPU.Build.0 = Release|Any CPU {60FE92B2-095B-46C3-8FEC-8C2350E92884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {60FE92B2-095B-46C3-8FEC-8C2350E92884}.Debug|Any CPU.Build.0 = Debug|Any CPU {60FE92B2-095B-46C3-8FEC-8C2350E92884}.Release|Any CPU.ActiveCfg = Release|Any CPU {60FE92B2-095B-46C3-8FEC-8C2350E92884}.Release|Any CPU.Build.0 = Release|Any CPU - {AE88171D-FB16-4686-B23E-1C5F75DF7DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE88171D-FB16-4686-B23E-1C5F75DF7DC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE88171D-FB16-4686-B23E-1C5F75DF7DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE88171D-FB16-4686-B23E-1C5F75DF7DC2}.Release|Any CPU.Build.0 = Release|Any CPU - {47131B79-A7E2-4A46-B9E8-98DDC0C20FCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47131B79-A7E2-4A46-B9E8-98DDC0C20FCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47131B79-A7E2-4A46-B9E8-98DDC0C20FCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47131B79-A7E2-4A46-B9E8-98DDC0C20FCE}.Release|Any CPU.Build.0 = Release|Any CPU {2569CD34-41EF-42A3-AE63-BE01A0124D44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2569CD34-41EF-42A3-AE63-BE01A0124D44}.Debug|Any CPU.Build.0 = Debug|Any CPU {2569CD34-41EF-42A3-AE63-BE01A0124D44}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -104,5 +103,33 @@ Global {7FE22FFD-CB25-446A-9192-76127270C3E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FE22FFD-CB25-446A-9192-76127270C3E1}.Release|Any CPU.ActiveCfg = Release|Any CPU {7FE22FFD-CB25-446A-9192-76127270C3E1}.Release|Any CPU.Build.0 = Release|Any CPU + {3009ACF8-F2DC-492C-8E41-7E36AB3A7A6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3009ACF8-F2DC-492C-8E41-7E36AB3A7A6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3009ACF8-F2DC-492C-8E41-7E36AB3A7A6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3009ACF8-F2DC-492C-8E41-7E36AB3A7A6C}.Release|Any CPU.Build.0 = Release|Any CPU + {2FF4D33E-2964-41B3-B0B2-3C9D70616FBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FF4D33E-2964-41B3-B0B2-3C9D70616FBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FF4D33E-2964-41B3-B0B2-3C9D70616FBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FF4D33E-2964-41B3-B0B2-3C9D70616FBA}.Release|Any CPU.Build.0 = Release|Any CPU + {75110834-EBD6-450A-9092-B619CE02FEAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75110834-EBD6-450A-9092-B619CE02FEAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75110834-EBD6-450A-9092-B619CE02FEAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75110834-EBD6-450A-9092-B619CE02FEAC}.Release|Any CPU.Build.0 = Release|Any CPU + {CDA31FB1-04BA-4A5D-9282-7EBAB755BE7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDA31FB1-04BA-4A5D-9282-7EBAB755BE7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDA31FB1-04BA-4A5D-9282-7EBAB755BE7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDA31FB1-04BA-4A5D-9282-7EBAB755BE7E}.Release|Any CPU.Build.0 = Release|Any CPU + {423ECCA1-DBC1-4947-8BD6-AAF3A8A55B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {423ECCA1-DBC1-4947-8BD6-AAF3A8A55B37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {423ECCA1-DBC1-4947-8BD6-AAF3A8A55B37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {423ECCA1-DBC1-4947-8BD6-AAF3A8A55B37}.Release|Any CPU.Build.0 = Release|Any CPU + {850C0239-46E5-4B2B-BBE8-954C4E93EDB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {850C0239-46E5-4B2B-BBE8-954C4E93EDB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {850C0239-46E5-4B2B-BBE8-954C4E93EDB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {850C0239-46E5-4B2B-BBE8-954C4E93EDB4}.Release|Any CPU.Build.0 = Release|Any CPU + {FFE529A5-CF9D-46F6-9682-A23ED32B6648}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFE529A5-CF9D-46F6-9682-A23ED32B6648}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFE529A5-CF9D-46F6-9682-A23ED32B6648}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFE529A5-CF9D-46F6-9682-A23ED32B6648}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 1250ff1..0dadc8b 100644 --- a/README.md +++ b/README.md @@ -5,26 +5,19 @@ ![Discord](https://img.shields.io/discord/1096095159860600832?label=Discord&logo=Discord&style=for-the-badge) Coflo is an in development, distrobuted workflow engine. + ## Roadmap - Additional browser support - Add more integrations - ## Run Locally - - - ## Features - - - ## Acknowledgements - ## Appendix ## Contributing diff --git a/source/infrastructure/messaging/Coflo.Infrastructure.Messaging.RabbitMQ/Coflo.Infrastructure.Messaging.RabbitMQ.csproj b/source/activities/Coflo.Activities.Primitives/Coflo.Activities.Primitives.csproj similarity index 66% rename from source/infrastructure/messaging/Coflo.Infrastructure.Messaging.RabbitMQ/Coflo.Infrastructure.Messaging.RabbitMQ.csproj rename to source/activities/Coflo.Activities.Primitives/Coflo.Activities.Primitives.csproj index 6836c68..2ef04c3 100644 --- a/source/infrastructure/messaging/Coflo.Infrastructure.Messaging.RabbitMQ/Coflo.Infrastructure.Messaging.RabbitMQ.csproj +++ b/source/activities/Coflo.Activities.Primitives/Coflo.Activities.Primitives.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/source/activities/Coflo.Activities.Primitives/Control/If.cs b/source/activities/Coflo.Activities.Primitives/Control/If.cs new file mode 100644 index 0000000..568a613 --- /dev/null +++ b/source/activities/Coflo.Activities.Primitives/Control/If.cs @@ -0,0 +1,40 @@ +using Coflo.Abstractions.Activities.Attributes; +using Coflo.Abstractions.Activities.Contracts; +using Coflo.Abstractions.Activities.Enums; +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Evaluation.Contracts; +using Coflo.Abstractions.Evaluation.Models; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Activities.Primitives.Control; + +[Activity(DisplayName = "If", Outcomes = new[] { "True", "False" }, Category = ActivityCategory.Decision)] +public class If : Activity +{ + private readonly IEvaluator _evaluator; + + [ActivityInput(DisplayName = "Condition")] + public string Condition { get; set; } = "false"; + + public If(IEvaluator evaluator) : base("IF") + { + _evaluator = evaluator; + } + + public override async Task ExecuteAsync(ActivityExecutionContext context) + { + try + { + var evaluationContext = new EvaluationContext(context.Variables, Condition); + var result = await _evaluator.EvaluateAsync(evaluationContext); + + return new ActivityExecutionResult(result ? "True" : "False", true, ActivityStatus.Completed, + new VariableCollection(), context.WorkflowInstanceId, context.ActivityInstanceId); + } + catch (Exception e) + { + return new ActivityExecutionResult(string.Empty, false, ActivityStatus.Faulted, + new VariableCollection(), context.WorkflowInstanceId, context.ActivityInstanceId); + } + } +} \ No newline at end of file diff --git a/source/activities/Coflo.Activities.Primitives/Variables/SetVariable.cs b/source/activities/Coflo.Activities.Primitives/Variables/SetVariable.cs new file mode 100644 index 0000000..f973647 --- /dev/null +++ b/source/activities/Coflo.Activities.Primitives/Variables/SetVariable.cs @@ -0,0 +1,36 @@ +using Coflo.Abstractions.Activities.Attributes; +using Coflo.Abstractions.Activities.Contracts; +using Coflo.Abstractions.Activities.Enums; +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Activities.Primitives.Variables; + +[Activity(DisplayName = "Set Variable", Category = ActivityCategory.Workflow, Outcomes = new[] { Done, Failed })] +public class SetVariable : Activity +{ + private const string Done = "Done"; + private const string Failed = "Failed"; + + [ActivityInput(DisplayName = "Variable")] + public VariableDefinition Variable { get; set; } + + [ActivityInput(DisplayName = "Value")] + public object? Value { get; set; } + + public SetVariable() : base("SET_VARIABLE") + { + } + + public override Task ExecuteAsync(ActivityExecutionContext context) + { + var variable = new VariableInstance(Variable); + variable.SetValue(Value); + var isUpdated = context.Variables.AddOrUpdate(variable); + + var result = new ActivityExecutionResult(isUpdated ? Done : Failed, isUpdated, ActivityStatus.Completed, context.Variables, + context.WorkflowInstanceId, context.ActivityInstanceId); + + return Task.FromResult(result as IActivityExecutionResult); + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Attributes/ActivityAttribute.cs b/source/core/Coflo.Abstractions/Activities/Attributes/ActivityAttribute.cs new file mode 100644 index 0000000..dc0fcd0 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Attributes/ActivityAttribute.cs @@ -0,0 +1,11 @@ +using Coflo.Abstractions.Activities.Enums; + +namespace Coflo.Abstractions.Activities.Attributes; + +public class ActivityAttribute : Attribute +{ + public string DisplayName { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public ActivityCategory Category { get; set; } = ActivityCategory.None; + public string[] Outcomes { get; set; } = { OutcomeNames.Failure, OutcomeNames.Success }; +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Attributes/ActivityInputAttribute.cs b/source/core/Coflo.Abstractions/Activities/Attributes/ActivityInputAttribute.cs new file mode 100644 index 0000000..07b7407 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Attributes/ActivityInputAttribute.cs @@ -0,0 +1,7 @@ +namespace Coflo.Abstractions.Activities.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class ActivityInputAttribute : Attribute +{ + public string DisplayName { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Attributes/ActivityOutputAttribute.cs b/source/core/Coflo.Abstractions/Activities/Attributes/ActivityOutputAttribute.cs new file mode 100644 index 0000000..b808858 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Attributes/ActivityOutputAttribute.cs @@ -0,0 +1,7 @@ +namespace Coflo.Abstractions.Activities.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class ActivityOutputAttribute : Attribute +{ + public string DisplayName { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Commands/StartActivityCommand.cs b/source/core/Coflo.Abstractions/Activities/Commands/StartActivityCommand.cs new file mode 100644 index 0000000..3af74db --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Commands/StartActivityCommand.cs @@ -0,0 +1,17 @@ +using Coflo.Abstractions.Variables.Model; +using Mediator; + +namespace Coflo.Abstractions.Activities.Commands; + +public class StartActivityCommand : ICommand +{ + public long WorkflowInstanceId { get; set; } + public long ActivityDefinitionId { get; set; } + public IVariableCollection VariableCollection { get; set; } + public StartActivityCommand(long workflowInstanceId, long activityDefinitionId, IVariableCollection variableCollection) + { + WorkflowInstanceId = workflowInstanceId; + ActivityDefinitionId = activityDefinitionId; + VariableCollection = variableCollection; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Contracts/IActivity.cs b/source/core/Coflo.Abstractions/Activities/Contracts/IActivity.cs new file mode 100644 index 0000000..07edc11 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Contracts/IActivity.cs @@ -0,0 +1,6 @@ +namespace Coflo.Abstractions.Activities.Contracts; + +public interface IActivity : IActivityBody +{ + public string Name { get; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Contracts/IActivityBody.cs b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityBody.cs new file mode 100644 index 0000000..4422871 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityBody.cs @@ -0,0 +1,8 @@ +using Coflo.Abstractions.Activities.Models; + +namespace Coflo.Abstractions.Activities.Contracts; + +public interface IActivityBody +{ + Task ExecuteAsync(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Contracts/IActivityExecutionContext.cs b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityExecutionContext.cs new file mode 100644 index 0000000..0340784 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityExecutionContext.cs @@ -0,0 +1,11 @@ +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Contracts; + +public interface IActivityExecutionContext +{ + public VariableCollection Variables { get; set; } + public long WorkflowInstanceId { get; set; } + public long ActivityInstanceId { get; set; } + public string ActivityName { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Contracts/IActivityExecutionResult.cs b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityExecutionResult.cs new file mode 100644 index 0000000..a3eaeec --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityExecutionResult.cs @@ -0,0 +1,14 @@ +using Coflo.Abstractions.Activities.Enums; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Contracts; + +public interface IActivityExecutionResult +{ + public bool IsSuccessful { get; set; } + public ActivityStatus Status { get; set; } + public IVariableCollection VariableCollection { get; set; } + public long WorkflowInstanceId { get; set; } + public long ActivityInstanceId { get; set; } + public string Outcome { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Contracts/IActivityInstance.cs b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityInstance.cs new file mode 100644 index 0000000..d0bad0e --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Contracts/IActivityInstance.cs @@ -0,0 +1,14 @@ +using Coflo.Abstractions.Activities.Enums; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Contracts; + +public interface IActivityInstance +{ + public long WorkflowInstanceId { get; set; } + public long ActivityInstanceId { get; set; } + public string ActivityName { get; set; } + public ActivityInstanceStatus Status { get; set; } + + public VariableCollection VariableCollection { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Enums/ActivityCategory.cs b/source/core/Coflo.Abstractions/Activities/Enums/ActivityCategory.cs new file mode 100644 index 0000000..b5ec622 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Enums/ActivityCategory.cs @@ -0,0 +1,17 @@ +namespace Coflo.Abstractions.Activities.Enums; + +public enum ActivityCategory +{ + Action, + Decision, + Loop, + Scope, + Sequence, + Switch, + Terminate, + Throw, + Try, + Wait, + Workflow, + None +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Enums/ActivityInstanceStatus.cs b/source/core/Coflo.Abstractions/Activities/Enums/ActivityInstanceStatus.cs new file mode 100644 index 0000000..fc5d5cb --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Enums/ActivityInstanceStatus.cs @@ -0,0 +1,10 @@ +namespace Coflo.Abstractions.Activities.Enums; + +public enum ActivityInstanceStatus +{ + Created, + Running, + Completed, + Faulted, + Canceled +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Enums/ActivityStatus.cs b/source/core/Coflo.Abstractions/Activities/Enums/ActivityStatus.cs new file mode 100644 index 0000000..df2a916 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Enums/ActivityStatus.cs @@ -0,0 +1,10 @@ +namespace Coflo.Abstractions.Activities.Enums; + +public enum ActivityStatus +{ + Waiting, + Executing, + Completed, + Faulted, + Canceled +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Models/Activity.cs b/source/core/Coflo.Abstractions/Activities/Models/Activity.cs new file mode 100644 index 0000000..570fc66 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Models/Activity.cs @@ -0,0 +1,15 @@ +using Coflo.Abstractions.Activities.Contracts; + +namespace Coflo.Abstractions.Activities.Models; + +public abstract class Activity : IActivity +{ + protected Activity(string name) + { + Name = name; + } + + public string Name { get; } + + public abstract Task ExecuteAsync(ActivityExecutionContext context); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Models/ActivityDefinition.cs b/source/core/Coflo.Abstractions/Activities/Models/ActivityDefinition.cs new file mode 100644 index 0000000..dffc493 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Models/ActivityDefinition.cs @@ -0,0 +1,18 @@ +using Coflo.Abstractions.Connections.Contracts; + +namespace Coflo.Abstractions.Activities.Models; + +public class ActivityDefinition +{ + public long WorkflowDefinitionId { get; set; } + public long WorkflowVersionId { get; set; } + public long ActivityDefinitionId { get; set; } + public string DisplayName { get; set; } + public string ActivityName { get; set; } + + public ICollection InputMappings { get; set; } = new List(); + public ICollection OutputMappings { get; set; } = new List(); + + public ICollection InputNode { get; set; } = new List(); + public ICollection OutputNodes { get; set; } = new List(); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Models/ActivityExecutionContext.cs b/source/core/Coflo.Abstractions/Activities/Models/ActivityExecutionContext.cs new file mode 100644 index 0000000..fa7726a --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Models/ActivityExecutionContext.cs @@ -0,0 +1,19 @@ +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Models; + +public class ActivityExecutionContext +{ + public ActivityExecutionContext(IVariableCollection variables, long workflowInstanceId, long activityInstanceId, string activityName) + { + Variables = variables; + WorkflowInstanceId = workflowInstanceId; + ActivityInstanceId = activityInstanceId; + ActivityName = activityName; + } + + public IVariableCollection Variables { get; set; } + public long WorkflowInstanceId { get; set; } + public long ActivityInstanceId { get; set; } + public string ActivityName { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Models/ActivityExecutionResult.cs b/source/core/Coflo.Abstractions/Activities/Models/ActivityExecutionResult.cs new file mode 100644 index 0000000..d5bc513 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Models/ActivityExecutionResult.cs @@ -0,0 +1,24 @@ +using Coflo.Abstractions.Activities.Contracts; +using Coflo.Abstractions.Activities.Enums; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Models; + +public class ActivityExecutionResult : IActivityExecutionResult +{ + public bool IsSuccessful { get; set; } + public ActivityStatus Status { get; set; } + public IVariableCollection VariableCollection { get; set; } + public long WorkflowInstanceId { get; set; } + public long ActivityInstanceId { get; set; } + public string Outcome { get; set; } + + public ActivityExecutionResult(string outcome, bool isSuccessful, ActivityStatus status, + IVariableCollection variableCollection, long workflowInstanceId, long activityInstanceId) + { + Outcome = outcome; + IsSuccessful = isSuccessful; + Status = status; + VariableCollection = variableCollection; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Models/ActivityInputMapping.cs b/source/core/Coflo.Abstractions/Activities/Models/ActivityInputMapping.cs new file mode 100644 index 0000000..224887d --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Models/ActivityInputMapping.cs @@ -0,0 +1,10 @@ +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Models; + +public class ActivityInputMapping +{ + public string ActivityInputField { get; set; } + public VariableDefinition? VariableDefinition { get; set; } + public object? Literal { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Models/ActivityOutputMapping.cs b/source/core/Coflo.Abstractions/Activities/Models/ActivityOutputMapping.cs new file mode 100644 index 0000000..7ca27e4 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Models/ActivityOutputMapping.cs @@ -0,0 +1,9 @@ +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Activities.Models; + +public class ActivityOutputMapping +{ + public string ActivityOutputField { get; set; } + public VariableDefinition? VariableDefinition { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/OutcomeNames.cs b/source/core/Coflo.Abstractions/Activities/OutcomeNames.cs new file mode 100644 index 0000000..dbf5109 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/OutcomeNames.cs @@ -0,0 +1,7 @@ +namespace Coflo.Abstractions.Activities; + +public class OutcomeNames +{ + public const string Success = "Success"; + public const string Failure = "Failure"; +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Services/IActivityExecutor.cs b/source/core/Coflo.Abstractions/Activities/Services/IActivityExecutor.cs new file mode 100644 index 0000000..a8620fe --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Services/IActivityExecutor.cs @@ -0,0 +1,12 @@ +using Coflo.Abstractions.Activities.Contracts; +using Coflo.Abstractions.Connections.Contracts; +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Contracts; +using Coflo.Abstractions.Workflows.Stores; + +namespace Coflo.Abstractions.Activities.Services; + +public interface IActivityExecutor +{ + Task ExecuteAsync(IActivity activity, VariableCollection variables, long workflowInstanceId, long nodeId); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Activities/Stores/IActivityDefinitionStore.cs b/source/core/Coflo.Abstractions/Activities/Stores/IActivityDefinitionStore.cs new file mode 100644 index 0000000..3fbd076 --- /dev/null +++ b/source/core/Coflo.Abstractions/Activities/Stores/IActivityDefinitionStore.cs @@ -0,0 +1,10 @@ +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Abstractions.Activities.Stores; + +public interface IActivityDefinitionStore +{ + ValueTask GetActivityDefinitionAsync(long activityDefinitionId); + ValueTask GetWorkflowDefinitionVersionAsync(long workflowDefinitionVersionId); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Caching/Contracts/ICacheProvider.cs b/source/core/Coflo.Abstractions/Caching/Contracts/ICacheProvider.cs new file mode 100644 index 0000000..8c988a2 --- /dev/null +++ b/source/core/Coflo.Abstractions/Caching/Contracts/ICacheProvider.cs @@ -0,0 +1,12 @@ +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Abstractions.Caching.Contracts; + +public interface ICacheProvider +{ + Task Insert(string key, T value); + ValueTask Get(string key); + ValueTask Exists(string key); + Task Delete(string key); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj b/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj index 4b43e18..c9d98dc 100644 --- a/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj +++ b/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj @@ -7,13 +7,14 @@ + + - - + diff --git a/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj.DotSettings b/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj.DotSettings new file mode 100644 index 0000000..9163818 --- /dev/null +++ b/source/core/Coflo.Abstractions/Coflo.Abstractions.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Connections/Contracts/IConnection.cs b/source/core/Coflo.Abstractions/Connections/Contracts/IConnection.cs new file mode 100644 index 0000000..d8adb16 --- /dev/null +++ b/source/core/Coflo.Abstractions/Connections/Contracts/IConnection.cs @@ -0,0 +1,10 @@ +using Coflo.Abstractions.Activities; + +namespace Coflo.Abstractions.Connections.Contracts; + +public interface IConnection +{ + public long ActivityId { get; set; } + public long TargetActivityId { get; set; } + public string Outcome { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Connections/Models/Connection.cs b/source/core/Coflo.Abstractions/Connections/Models/Connection.cs new file mode 100644 index 0000000..a39575c --- /dev/null +++ b/source/core/Coflo.Abstractions/Connections/Models/Connection.cs @@ -0,0 +1,11 @@ +using Coflo.Abstractions.Activities; +using Coflo.Abstractions.Connections.Contracts; + +namespace Coflo.Abstractions.Connections.Models; + +public class Connection : IConnection +{ + public long ActivityId { get; set; } + public long TargetActivityId { get; set; } + public string Outcome { get; set; } = OutcomeNames.Success; +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Contracts/Tenant/IApplicationTenant.cs b/source/core/Coflo.Abstractions/Contracts/Tenant/IApplicationTenant.cs deleted file mode 100644 index efc58d4..0000000 --- a/source/core/Coflo.Abstractions/Contracts/Tenant/IApplicationTenant.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Coflo.Abstractions.Contracts.Tenant; - -public interface IApplicationTenant -{ - public Guid TenantId { get; set; } - public string Name { get; set; } -} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Contracts/Tenant/ITenantScope.cs b/source/core/Coflo.Abstractions/Contracts/Tenant/ITenantScope.cs deleted file mode 100644 index 7928df0..0000000 --- a/source/core/Coflo.Abstractions/Contracts/Tenant/ITenantScope.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Coflo.Abstractions.Contracts.Tenant; - -public interface ITenantScope -{ - public Guid? TenantId { get; set; } -} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Evaluation/Contracts/IEvaluationContext.cs b/source/core/Coflo.Abstractions/Evaluation/Contracts/IEvaluationContext.cs new file mode 100644 index 0000000..00aac26 --- /dev/null +++ b/source/core/Coflo.Abstractions/Evaluation/Contracts/IEvaluationContext.cs @@ -0,0 +1,9 @@ +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Evaluation.Contracts; + +public interface IEvaluationContext +{ + IVariableCollection Variables { get; set; } + public string Condition { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Evaluation/Contracts/IEvaluator.cs b/source/core/Coflo.Abstractions/Evaluation/Contracts/IEvaluator.cs new file mode 100644 index 0000000..7485a88 --- /dev/null +++ b/source/core/Coflo.Abstractions/Evaluation/Contracts/IEvaluator.cs @@ -0,0 +1,6 @@ +namespace Coflo.Abstractions.Evaluation.Contracts; + +public interface IEvaluator +{ + Task EvaluateAsync(IEvaluationContext context); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Evaluation/Models/EvaluationContext.cs b/source/core/Coflo.Abstractions/Evaluation/Models/EvaluationContext.cs new file mode 100644 index 0000000..87466b7 --- /dev/null +++ b/source/core/Coflo.Abstractions/Evaluation/Models/EvaluationContext.cs @@ -0,0 +1,16 @@ +using Coflo.Abstractions.Evaluation.Contracts; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Evaluation.Models; + +public class EvaluationContext : IEvaluationContext +{ + public IVariableCollection Variables { get; set; } + public string Condition { get; set; } + + public EvaluationContext(IVariableCollection variables, string condition) + { + Variables = variables; + Condition = condition; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Events/Contracts/IEventPublisher.cs b/source/core/Coflo.Abstractions/Events/Contracts/IEventPublisher.cs new file mode 100644 index 0000000..2cae0f9 --- /dev/null +++ b/source/core/Coflo.Abstractions/Events/Contracts/IEventPublisher.cs @@ -0,0 +1,8 @@ +using Mediator; + +namespace Coflo.Abstractions.Events; + +public interface IEventPublisher +{ + Task PublishAsync(T @event) where T : IMessage; +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Models/Tenant/ApplicationTenant.cs b/source/core/Coflo.Abstractions/Models/Tenant/ApplicationTenant.cs deleted file mode 100644 index 28889c5..0000000 --- a/source/core/Coflo.Abstractions/Models/Tenant/ApplicationTenant.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Coflo.Abstractions.Contracts.Tenant; - -namespace Coflo.Abstractions.Models.Tenant; - -public class ApplicationTenant : IApplicationTenant -{ - public Guid TenantId { get; set; } - public string Name { get; set; } -} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Models/Workflow/WorkflowDefinition.cs b/source/core/Coflo.Abstractions/Models/Workflow/WorkflowDefinition.cs deleted file mode 100644 index afbbd94..0000000 --- a/source/core/Coflo.Abstractions/Models/Workflow/WorkflowDefinition.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Coflo.Abstractions.Contracts.Tenant; -using NodaTime; - -namespace Coflo.Abstractions.Models.Workflow; - -public class WorkflowDefinition : ITenantScope -{ - public Guid Id { get; set; } - public string Name { get; set; } - - public Guid? TenantId { get; set; } - - public ICollection Versions { get; set; } - - public Instant CreatedAt { get; set; } - public Instant UpdatedAt { get; set; } -} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Models/Workflow/WorkflowDefinitionVersion.cs b/source/core/Coflo.Abstractions/Models/Workflow/WorkflowDefinitionVersion.cs deleted file mode 100644 index 00c89d3..0000000 --- a/source/core/Coflo.Abstractions/Models/Workflow/WorkflowDefinitionVersion.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Coflo.Abstractions.Contracts.Tenant; -using NodaTime; - -namespace Coflo.Abstractions.Models.Workflow; - -public class WorkflowDefinitionVersion : ITenantScope -{ - public Guid WorkflowId { get; set; } - public long VersionId { get; set; } - public Guid? TenantId { get; set; } - - public Instant CreatedAt { get; set; } - public Instant UpdatedAt { get; set; } -} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Variables/Contracts/IVariableCollection.cs b/source/core/Coflo.Abstractions/Variables/Contracts/IVariableCollection.cs new file mode 100644 index 0000000..743fa8a --- /dev/null +++ b/source/core/Coflo.Abstractions/Variables/Contracts/IVariableCollection.cs @@ -0,0 +1,9 @@ +namespace Coflo.Abstractions.Variables.Model; + +public interface IVariableCollection +{ + VariableInstance? this[string name] { get; } + void Add(VariableInstance variable); + bool AddOrUpdate(VariableInstance variable); + void AddRange(IEnumerable variables); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Variables/Enums/VariableType.cs b/source/core/Coflo.Abstractions/Variables/Enums/VariableType.cs new file mode 100644 index 0000000..2fe51a8 --- /dev/null +++ b/source/core/Coflo.Abstractions/Variables/Enums/VariableType.cs @@ -0,0 +1,9 @@ +namespace Coflo.Abstractions.Variables.Enums; + +public enum VariableType +{ + String, + Number, + Boolean, + Object +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Variables/Model/VariableCollection.cs b/source/core/Coflo.Abstractions/Variables/Model/VariableCollection.cs new file mode 100644 index 0000000..b43b46c --- /dev/null +++ b/source/core/Coflo.Abstractions/Variables/Model/VariableCollection.cs @@ -0,0 +1,48 @@ +namespace Coflo.Abstractions.Variables.Model; + +public class VariableCollection : IVariableCollection +{ + private readonly IDictionary _variables; + + public VariableCollection(IDictionary variables) + { + _variables = new Dictionary(variables); + } + + public VariableCollection() : this(new Dictionary()) + { + + } + + public VariableInstance? this[string name] => _variables[name]; + + public bool Contains(string name) + { + return _variables.ContainsKey(name); + } + + public bool AddOrUpdate(VariableInstance variable) + { + if (Contains(variable.Name)) + { + _variables[variable.Name] = variable; + return true; + } + + _variables.Add(variable.Name, variable); + return false; + } + + public void Add(VariableInstance variable) + { + _variables.Add(variable.Name, variable); + } + + public void AddRange(IEnumerable variables) + { + foreach (var variable in variables) + { + Add(variable); + } + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Variables/Model/VariableDefinition.cs b/source/core/Coflo.Abstractions/Variables/Model/VariableDefinition.cs new file mode 100644 index 0000000..7f02c38 --- /dev/null +++ b/source/core/Coflo.Abstractions/Variables/Model/VariableDefinition.cs @@ -0,0 +1,52 @@ +using Coflo.Abstractions.Variables.Enums; + +namespace Coflo.Abstractions.Variables.Model; + +public class VariableDefinition +{ + public string Name { get; set; } + public VariableType VariableType { get; set; } + public bool IsArray { get; set; } + public object? DefaultValue { get; private set; } + public bool Nullable { get; set; } + public bool Persist { get; set; } = true; + public VariableDefinition(string name, VariableType variableType, bool isArray, object? defaultValue = null) + { + Name = name; + VariableType = variableType; + IsArray = isArray; + + if (!SetDefaultValue(defaultValue)) + throw new ArgumentException($"Invalid default value for variable type {variableType}"); + } + + public VariableDefinition(VariableDefinition variableDefinition) : this(variableDefinition.Name, + variableDefinition.VariableType, variableDefinition.IsArray, variableDefinition.DefaultValue) + { + } + + private bool SetDefaultValue(object? value) + { + switch (value) + { + case null when Nullable: + DefaultValue = null; + return true; + case string when VariableType == VariableType.String: + DefaultValue = value; + return true; + case int when VariableType == VariableType.Number: + DefaultValue = value; + return true; + case bool when VariableType == VariableType.Boolean: + DefaultValue = value; + return true; + } + + if (value.GetType() != typeof(object) || VariableType != VariableType.Object) return false; + + DefaultValue = value; + + return true; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Variables/Model/VariableInstance.cs b/source/core/Coflo.Abstractions/Variables/Model/VariableInstance.cs new file mode 100644 index 0000000..c12d736 --- /dev/null +++ b/source/core/Coflo.Abstractions/Variables/Model/VariableInstance.cs @@ -0,0 +1,61 @@ +using Coflo.Abstractions.Variables.Enums; + +namespace Coflo.Abstractions.Variables.Model; + +public class VariableInstance : VariableDefinition +{ + public VariableInstance(VariableDefinition definition) : base(definition) + { + + } + + public object? Value { get; private set; } + + public void SetValue(object? value) + { + if (value == null) + { + Value = null; + return; + } + + switch (value) + { + case string when VariableType == VariableType.String: + Value = value; + return; + case int when VariableType == VariableType.Number: + Value = value; + return; + case bool when VariableType == VariableType.Boolean: + Value = value; + return; + } + + if (value.GetType() != typeof(object) || VariableType != VariableType.Object) return; + + Value = value; + } + + public bool ValidateValue(object? value) + { + if (value == null) + { + return true; + } + + switch (value) + { + case string when VariableType == VariableType.String: + return true; + case int when VariableType == VariableType.Number: + return true; + case bool when VariableType == VariableType.Boolean: + return true; + } + + if (value.GetType() != typeof(object) || VariableType != VariableType.Object) return false; + + return true; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Commands/StartWorkflowExecutionCommand.cs b/source/core/Coflo.Abstractions/Workflows/Commands/StartWorkflowExecutionCommand.cs new file mode 100644 index 0000000..436d29a --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Commands/StartWorkflowExecutionCommand.cs @@ -0,0 +1,13 @@ +using Mediator; + +namespace Coflo.Abstractions.Workflows.Commands; + +public class StartWorkflowExecutionCommand : ICommand +{ + public long WorkflowInstanceId { get; set; } + + public StartWorkflowExecutionCommand(long workflowInstanceId) + { + WorkflowInstanceId = workflowInstanceId; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowDefinition.cs b/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowDefinition.cs new file mode 100644 index 0000000..8275c91 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowDefinition.cs @@ -0,0 +1,12 @@ +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Connections.Contracts; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Workflows.Contracts; + +public interface IWorkflowDefinition +{ + public long WorkflowDefinitionId { get; } + public string Name { get; } + public ICollection Versions { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowDefinitionVersion.cs b/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowDefinitionVersion.cs new file mode 100644 index 0000000..680d87b --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowDefinitionVersion.cs @@ -0,0 +1,16 @@ +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Connections.Contracts; +using Coflo.Abstractions.Variables.Model; + +namespace Coflo.Abstractions.Workflows.Contracts; + +public interface IWorkflowDefinitionVersion +{ + public long WorkflowVersionId { get; } + public long WorkflowDefinitionId { get; } + public IWorkflowDefinition WorkflowDefinition { get; } + + public ICollection Connections { get; set; } + public ICollection ActivityDefinitions { get; set; } + public ICollection VariableDefinitions { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowInstance.cs b/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowInstance.cs new file mode 100644 index 0000000..97fd68f --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Contracts/IWorkflowInstance.cs @@ -0,0 +1,20 @@ +using Coflo.Abstractions.Connections.Contracts; +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Models; +using NodaTime; + +namespace Coflo.Abstractions.Workflows.Contracts; + +public interface IWorkflowInstance +{ + public long InstanceId { get; set; } + public long WorkflowDefinitionId { get; set; } + public long WorkflowVersionId { get; set; } + + public IVariableCollection Variables { get; set; } + List WorkflowLogs { get; set; } + public WorkflowStatus Status { get; set; } + + public Instant CreatedAt { get; set; } + public Instant? CompletedAt { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Enums/WorkflowStatus.cs b/source/core/Coflo.Abstractions/Workflows/Enums/WorkflowStatus.cs new file mode 100644 index 0000000..03388bd --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Enums/WorkflowStatus.cs @@ -0,0 +1,10 @@ +namespace Coflo.Abstractions.Workflows; + +public enum WorkflowStatus +{ + Created, + Running, + Completed, + Failed, + Cancelled +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Models/WorkflowDefinition.cs b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowDefinition.cs new file mode 100644 index 0000000..4b3d2f3 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowDefinition.cs @@ -0,0 +1,14 @@ +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Connections.Contracts; +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Contracts; + +namespace Coflo.Abstractions.Workflows.Models; + +public class WorkflowDefinition : IWorkflowDefinition +{ + public long WorkflowDefinitionId { get; set; } + public long TenantId { get; set; } + public string Name { get; set; } + public ICollection Versions { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Models/WorkflowDefinitionVersion.cs b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowDefinitionVersion.cs new file mode 100644 index 0000000..4bd7d15 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowDefinitionVersion.cs @@ -0,0 +1,19 @@ +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Connections.Contracts; +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Contracts; + +namespace Coflo.Abstractions.Workflows.Models; + +public class WorkflowDefinitionVersion : IWorkflowDefinitionVersion +{ + public long WorkflowVersionId { get; set; } + public long WorkflowDefinitionId { get; set; } + public long TenantId { get; set; } + + public IWorkflowDefinition WorkflowDefinition { get; set; } + + public ICollection Connections { get; set; } + public ICollection ActivityDefinitions { get; set; } + public ICollection VariableDefinitions { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Models/WorkflowInstance.cs b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowInstance.cs new file mode 100644 index 0000000..77e6687 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowInstance.cs @@ -0,0 +1,20 @@ +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Contracts; +using NodaTime; + +namespace Coflo.Abstractions.Workflows.Models; + +public class WorkflowInstance : IWorkflowInstance +{ + public long InstanceId { get; set; } + public long WorkflowDefinitionId { get; set; } + public long WorkflowVersionId { get; set; } + public long TenantId { get; set; } + + public IVariableCollection Variables { get; set; } + public List WorkflowLogs { get; set; } + + public WorkflowStatus Status { get; set; } + public Instant CreatedAt { get; set; } + public Instant? CompletedAt { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Models/WorkflowLogEntry.cs b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowLogEntry.cs new file mode 100644 index 0000000..474c0ca --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Models/WorkflowLogEntry.cs @@ -0,0 +1,20 @@ +namespace Coflo.Abstractions.Workflows.Models; + +public class WorkflowLogEntry +{ + public long WorkflowInstanceId { get; set; } + public string Message { get; set; } + public object[] Params { get; set; } + + public WorkflowLogEntry(long workflowInstanceId, string message, params object[] @params) + { + WorkflowInstanceId = workflowInstanceId; + Message = message; + Params = @params; + } + + public override string ToString() + { + return string.Format(Message, Params); + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/ActivityCompletedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/ActivityCompletedNotification.cs new file mode 100644 index 0000000..6ed718c --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/ActivityCompletedNotification.cs @@ -0,0 +1,12 @@ +using Coflo.Abstractions.Variables.Model; +using Mediator; + +namespace Coflo.Abstractions.Workflows.Notifications; + +public class ActivityCompletedNotification : INotification +{ + public long NodeId { get; set; } + public long WorkflowInstanceId { get; set; } + public IVariableCollection Variables { get; set; } + public string Outcome { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/ActivityFailedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/ActivityFailedNotification.cs new file mode 100644 index 0000000..fe4ae48 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/ActivityFailedNotification.cs @@ -0,0 +1,8 @@ +namespace Coflo.Abstractions.Workflows.Notifications; + +public class ActivityFailedNotification +{ + public long NodeId { get; set; } + public long WorkflowInstanceId { get; set; } + public string Error { get; set; } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowCompletedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowCompletedNotification.cs new file mode 100644 index 0000000..e961c66 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowCompletedNotification.cs @@ -0,0 +1,13 @@ +using Mediator; + +namespace Coflo.Abstractions.Workflows.Notifications; + +public class WorkflowCompletedNotification : INotification +{ + public long WorkflowInstanceId { get; } + + public WorkflowCompletedNotification(long workflowInstanceId) + { + WorkflowInstanceId = workflowInstanceId; + } +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionCompletedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionCompletedNotification.cs new file mode 100644 index 0000000..98cc2e0 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionCompletedNotification.cs @@ -0,0 +1,8 @@ +using Mediator; + +namespace Coflo.Abstractions.Workflows.Notifications; + +public class WorkflowExecutionCompletedNotification : INotification +{ + +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionFailedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionFailedNotification.cs new file mode 100644 index 0000000..c63e063 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionFailedNotification.cs @@ -0,0 +1,8 @@ +using Mediator; + +namespace Coflo.Abstractions.Workflows.Notifications; + +public class WorkflowExecutionFailedNotification : INotification +{ + +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionResumedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionResumedNotification.cs new file mode 100644 index 0000000..674ccb2 --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionResumedNotification.cs @@ -0,0 +1,8 @@ +using Mediator; + +namespace Coflo.Abstractions.Workflows.Notifications; + +public class WorkflowExecutionResumedNotification : INotification +{ + +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionTerminatedNotification.cs b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionTerminatedNotification.cs new file mode 100644 index 0000000..16bfbac --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Notifications/WorkflowExecutionTerminatedNotification.cs @@ -0,0 +1,8 @@ +using Mediator; + +namespace Coflo.Abstractions.Workflows.Notifications; + +public class WorkflowExecutionTerminatedNotification : INotification +{ + +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Repositories/IWorkflowDefinitionRepository.cs b/source/core/Coflo.Abstractions/Workflows/Repositories/IWorkflowDefinitionRepository.cs new file mode 100644 index 0000000..a18812b --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Repositories/IWorkflowDefinitionRepository.cs @@ -0,0 +1,13 @@ +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Infrastructure.Persistance.Cassandra.Repositories; + +public interface IWorkflowDefinitionRepository +{ + ValueTask GetWorkflowDefinition(long workflowDefinitionId); + ValueTask GetWorkflowDefinitionVersion(long workflowDefinitionId, + long workflowVersionId); + Task InsertWorkflowDefinition(WorkflowDefinition workflowDefinition); + Task InsertWorkflowDefinitionVersion(WorkflowDefinitionVersion workflowDefinitionVersion); + Task RemoveWorkflowDefinition(long workflowDefinitionId); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Services/IWorkflowExecutor.cs b/source/core/Coflo.Abstractions/Workflows/Services/IWorkflowExecutor.cs new file mode 100644 index 0000000..8c1c4ae --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Services/IWorkflowExecutor.cs @@ -0,0 +1,18 @@ +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Notifications; + +namespace Coflo.Abstractions.Workflows.Services; + +public interface IWorkflowExecutor +{ + Task InitializeWorkflow(long workflowDefinitionId, long workflowVersionId, + VariableCollection variables); + + Task ExecuteWorkflow(long workflowInstanceId); + + Task ActivityCompleted(ActivityCompletedNotification completedNotification); + + Task ActivityFailed(ActivityFailedNotification failedNotification); + + Task WorkflowCompleted(long workflowInstanceId); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Stores/IWorkflowDefinitionStore.cs b/source/core/Coflo.Abstractions/Workflows/Stores/IWorkflowDefinitionStore.cs new file mode 100644 index 0000000..eabdf2d --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Stores/IWorkflowDefinitionStore.cs @@ -0,0 +1,17 @@ +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Abstractions.Workflows.Stores; + +public interface IWorkflowDefinitionStore +{ + ValueTask GetWorkflowDefinitionAsync(long workflowDefinitionId); + ValueTask GetWorkflowDefinitionVersionAsync(long workflowDefinitionId, + long workflowVersionId); + Task> GetWorkflowDefinitionVersionsAsync(long workflowDefinitionId); + + Task SaveWorkflowDefinitionAsync(WorkflowDefinition workflowDefinition); + Task SaveWorkflowDefinitionVersionAsync(WorkflowDefinition workflowDefinition); + + Task DeleteWorkflowDefinitionAsync(long workflowDefinitionId); + Task DeleteWorkflowDefinitionVersionAsync(long workflowDefinitionId, long workflowVersionId); +} \ No newline at end of file diff --git a/source/core/Coflo.Abstractions/Workflows/Stores/IWorkflowInstanceStore.cs b/source/core/Coflo.Abstractions/Workflows/Stores/IWorkflowInstanceStore.cs new file mode 100644 index 0000000..da2295d --- /dev/null +++ b/source/core/Coflo.Abstractions/Workflows/Stores/IWorkflowInstanceStore.cs @@ -0,0 +1,13 @@ +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Abstractions.Workflows.Stores; + +public interface IWorkflowInstanceStore +{ + Task GetWorkflowInstanceAsync(long workflowInstanceId); + Task> GetWorkflowInstancesAsync(long workflowDefinitionId); + + Task SaveWorkflowInstanceAsync(WorkflowInstance workflowInstance, bool persist = false); + + Task DeleteWorkflowInstanceAsync(long workflowInstanceId); +} \ No newline at end of file diff --git a/source/core/Coflo.Core/Coflo.Core.csproj b/source/core/Coflo.Core/Coflo.Core.csproj new file mode 100644 index 0000000..cb005f1 --- /dev/null +++ b/source/core/Coflo.Core/Coflo.Core.csproj @@ -0,0 +1,22 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/source/core/Coflo.Core/Services/CapEventPublisher.cs b/source/core/Coflo.Core/Services/CapEventPublisher.cs new file mode 100644 index 0000000..eecad84 --- /dev/null +++ b/source/core/Coflo.Core/Services/CapEventPublisher.cs @@ -0,0 +1,19 @@ +using Coflo.Abstractions.Events; +using DotNetCore.CAP; +using Mediator; + +namespace Coflo.Core.Services; + +public class CapEventPublisher : IEventPublisher +{ + private readonly ICapPublisher _capPublisher; + + public CapEventPublisher(ICapPublisher capPublisher) + { + _capPublisher = capPublisher; + } + + + public Task PublishAsync(T @event) where T : IMessage + => _capPublisher.PublishAsync(nameof(@event), @event); +} \ No newline at end of file diff --git a/source/core/Coflo.SDK/Class1.cs b/source/core/Coflo.SDK/Class1.cs deleted file mode 100644 index 150f87b..0000000 --- a/source/core/Coflo.SDK/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Coflo.SDK; - -public class Class1 -{ -} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Api.GRPC/Coflo.Hosting.Api.GRPC.csproj b/source/hosting/Coflo.Core.Snowflake/Coflo.Core.Snowflake.csproj similarity index 54% rename from source/hosting/Coflo.Hosting.Api.GRPC/Coflo.Hosting.Api.GRPC.csproj rename to source/hosting/Coflo.Core.Snowflake/Coflo.Core.Snowflake.csproj index 6836c68..e74d845 100644 --- a/source/hosting/Coflo.Hosting.Api.GRPC/Coflo.Hosting.Api.GRPC.csproj +++ b/source/hosting/Coflo.Core.Snowflake/Coflo.Core.Snowflake.csproj @@ -6,4 +6,9 @@ enable + + + + + diff --git a/source/hosting/Coflo.Core.Snowflake/Generators/IIdGenerator.cs b/source/hosting/Coflo.Core.Snowflake/Generators/IIdGenerator.cs new file mode 100644 index 0000000..130b277 --- /dev/null +++ b/source/hosting/Coflo.Core.Snowflake/Generators/IIdGenerator.cs @@ -0,0 +1,6 @@ +namespace Coflo.Core.Snowflake.Generators; + +public interface IIdGenerator +{ + Task NextId(); +} \ No newline at end of file diff --git a/source/hosting/Coflo.Core.Snowflake/Generators/IdGenerator.cs b/source/hosting/Coflo.Core.Snowflake/Generators/IdGenerator.cs new file mode 100644 index 0000000..968eeda --- /dev/null +++ b/source/hosting/Coflo.Core.Snowflake/Generators/IdGenerator.cs @@ -0,0 +1,82 @@ +using Coflo.Core.Snowflake.Models; +using Microsoft.Extensions.Configuration; +using NodaTime; + +namespace Coflo.Core.Snowflake.Generators; + +public class IdGenerator : IIdGenerator +{ + private const long TimestampBits = 41; + private const long SequenceBits = 8; + private const long MachineIdBits = 63 - TimestampBits - SequenceBits; + private const long Epoch = 1_638_400_000_000L; + private readonly IClock _clock; + private readonly long _machineId; + + private readonly Mutex _mutex; + private long _lastTimestamp = -1L; + private long _sequence; + + public IdGenerator(IClock clock, IConfiguration configuration) + { + _clock = clock; + _machineId = configuration.GetValue("MachineId"); + + _mutex = new Mutex(); + } + + public Task NextId() + { + var timestamp = GetTimestamp(); + + _mutex.WaitOne(); + + if (timestamp == _lastTimestamp) + { + _sequence = (_sequence + 1) & ((1 << (int)SequenceBits) - 1); + + if (_sequence == 0) timestamp = GetNextTimestamp(); + } + else + { + _sequence = 0; + } + + _lastTimestamp = timestamp; + + _mutex.ReleaseMutex(); + + return Task.FromResult(EncodeId(_clock.GetCurrentInstant(), _machineId, _sequence)); + } + + private long GetTimestamp() + { + return _clock.GetCurrentInstant().ToUnixTimeMilliseconds() - Epoch; + } + + private long GetNextTimestamp() + { + var timestamp = GetTimestamp(); + + while (timestamp <= _lastTimestamp) timestamp = GetTimestamp(); + + return timestamp; + } + + internal static long EncodeId(Instant timestamp, long machineId, long sequence) + { + var timestampDelta = timestamp.ToUnixTimeMilliseconds() - Epoch; + var id = (timestampDelta << (int)(SequenceBits + MachineIdBits)) | (machineId << (int)SequenceBits) | sequence; + + return id; + } + + public static SnowflakeId DecodeId(long id) + { + var sequence = id & ((1 << (int)SequenceBits) - 1); + var machineId = (id >> (int)SequenceBits) & ((1 << (int)MachineIdBits) - 1); + var timestamp = (id >> (int)(SequenceBits + MachineIdBits)) + Epoch; + + return SnowflakeId.Create(id, Instant.FromUnixTimeMilliseconds(timestamp), machineId, sequence); + } +} \ No newline at end of file diff --git a/source/hosting/Coflo.Core.Snowflake/Models/SnowflakeId.cs b/source/hosting/Coflo.Core.Snowflake/Models/SnowflakeId.cs new file mode 100644 index 0000000..a398c58 --- /dev/null +++ b/source/hosting/Coflo.Core.Snowflake/Models/SnowflakeId.cs @@ -0,0 +1,22 @@ +using NodaTime; + +namespace Coflo.Core.Snowflake.Models; + +public class SnowflakeId +{ + public long Id { get; private set; } + public Instant Timestamp { get; private set; } + public long MachineId { get; private set; } + public long Sequence { get; private set; } + + internal static SnowflakeId Create(long id, Instant timestamp, long machineId, long sequence) + { + return new SnowflakeId + { + Id = id, + Timestamp = timestamp, + MachineId = machineId, + Sequence = sequence + }; + } +} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Api.GRPC/Class1.cs b/source/hosting/Coflo.Hosting.Api.GRPC/Class1.cs deleted file mode 100644 index 9132b5a..0000000 --- a/source/hosting/Coflo.Hosting.Api.GRPC/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Coflo.Hosting.Api.GRPC; - -public class Class1 -{ -} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Api.Rest/Class1.cs b/source/hosting/Coflo.Hosting.Api.Rest/Class1.cs deleted file mode 100644 index 259b202..0000000 --- a/source/hosting/Coflo.Hosting.Api.Rest/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Coflo.Hosting.Api.Rest; - -public class Class1 -{ -} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Api.Rest/Coflo.Hosting.Api.Rest.csproj b/source/hosting/Coflo.Hosting.Api.Rest/Coflo.Hosting.Api.Rest.csproj deleted file mode 100644 index 6836c68..0000000 --- a/source/hosting/Coflo.Hosting.Api.Rest/Coflo.Hosting.Api.Rest.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net7.0 - enable - enable - - - diff --git a/source/hosting/Coflo.Hosting.Orchestrator/CapControllers/WorkflowEventController.cs b/source/hosting/Coflo.Hosting.Orchestrator/CapControllers/WorkflowEventController.cs new file mode 100644 index 0000000..9643303 --- /dev/null +++ b/source/hosting/Coflo.Hosting.Orchestrator/CapControllers/WorkflowEventController.cs @@ -0,0 +1,57 @@ +using Coflo.Abstractions.Workflows.Commands; +using Coflo.Abstractions.Workflows.Notifications; +using DotNetCore.CAP; +using Mediator; +using Microsoft.AspNetCore.Mvc; + +namespace Coflo.Hosting.Orchestrator.CapControllers; + +public class WorkflowEventController : ControllerBase +{ + private readonly IMediator _mediator; + + public WorkflowEventController(IMediator mediator) + { + _mediator = mediator; + } + + [NonAction] + [CapSubscribe(nameof(StartWorkflowExecutionCommand))] + public async Task StartWorkflowExecutionEvent(StartWorkflowExecutionCommand @event, + CancellationToken cancellationToken = default!) + { + await _mediator.Publish(@event, cancellationToken); + } + + [NonAction] + [CapSubscribe(nameof(WorkflowExecutionCompletedNotification))] + public async Task WorkflowExecutionCompletedEvent(WorkflowExecutionCompletedNotification @event, + CancellationToken cancellationToken = default!) + { + await _mediator.Publish(@event, cancellationToken); + } + + [NonAction] + [CapSubscribe(nameof(WorkflowExecutionFailedNotification))] + public async Task WorkflowExecutionFailedEvent(WorkflowExecutionFailedNotification @event, + CancellationToken cancellationToken = default!) + { + await _mediator.Publish(@event, cancellationToken); + } + + [NonAction] + [CapSubscribe(nameof(WorkflowExecutionTerminatedNotification))] + public async Task WorkflowExecutionTerminatedEvent(WorkflowExecutionTerminatedNotification @event, + CancellationToken cancellationToken = default!) + { + await _mediator.Publish(@event, cancellationToken); + } + + [NonAction] + [CapSubscribe(nameof(WorkflowExecutionResumedNotification))] + public async Task WorkflowExecutionResumedEvent(WorkflowExecutionResumedNotification @event, + CancellationToken cancellationToken = default!) + { + await _mediator.Publish(@event, cancellationToken); + } +} \ No newline at end of file diff --git a/source/core/Coflo.SDK/Coflo.SDK.csproj b/source/hosting/Coflo.Hosting.Orchestrator/Coflo.Hosting.Orchestrator.csproj similarity index 66% rename from source/core/Coflo.SDK/Coflo.SDK.csproj rename to source/hosting/Coflo.Hosting.Orchestrator/Coflo.Hosting.Orchestrator.csproj index 6836c68..2ef04c3 100644 --- a/source/core/Coflo.SDK/Coflo.SDK.csproj +++ b/source/hosting/Coflo.Hosting.Orchestrator/Coflo.Hosting.Orchestrator.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/source/hosting/Coflo.Hosting.Orchestrator/ServiceCollectionExtensions.cs b/source/hosting/Coflo.Hosting.Orchestrator/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..a6068e7 --- /dev/null +++ b/source/hosting/Coflo.Hosting.Orchestrator/ServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using DotNetCore.CAP; +using Microsoft.Extensions.DependencyInjection; + +namespace Coflo.Hosting.Orchestrator; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddOrchestrator(this IServiceCollection services, Action capOptions) + { + services.AddMediator(); + services.AddCap(capOptions); + + + return services; + } +} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Orchestrator/Services/WorkflowExecutor.cs b/source/hosting/Coflo.Hosting.Orchestrator/Services/WorkflowExecutor.cs new file mode 100644 index 0000000..e9a4d44 --- /dev/null +++ b/source/hosting/Coflo.Hosting.Orchestrator/Services/WorkflowExecutor.cs @@ -0,0 +1,139 @@ +using Ardalis.GuardClauses; +using Coflo.Abstractions.Activities.Commands; +using Coflo.Abstractions.Activities.Stores; +using Coflo.Abstractions.Events; +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows; +using Coflo.Abstractions.Workflows.Commands; +using Coflo.Abstractions.Workflows.Models; +using Coflo.Abstractions.Workflows.Notifications; +using Coflo.Abstractions.Workflows.Services; +using Coflo.Abstractions.Workflows.Stores; +using Coflo.Core.Snowflake.Generators; +using NodaTime; + +namespace Coflo.Hosting.Orchestrator.Services; + +public class WorkflowExecutor : IWorkflowExecutor +{ + private readonly IWorkflowDefinitionStore _workflowDefinitionStore; + private readonly IWorkflowInstanceStore _workflowInstanceStore; + private readonly IActivityDefinitionStore _activityDefinitionStore; + private readonly IClock _clock; + private readonly IIdGenerator _idGenerator; + private readonly IEventPublisher _eventPublisher; + + public WorkflowExecutor(IWorkflowDefinitionStore workflowDefinitionStore, + IWorkflowInstanceStore workflowInstanceStore, IActivityDefinitionStore activityDefinitionStore, IClock clock, + IIdGenerator idGenerator, + IEventPublisher eventPublisher) + { + _workflowDefinitionStore = workflowDefinitionStore; + _workflowInstanceStore = workflowInstanceStore; + _activityDefinitionStore = activityDefinitionStore; + _clock = clock; + _idGenerator = idGenerator; + _eventPublisher = eventPublisher; + } + + public async Task InitializeWorkflow(long workflowDefinitionId, long workflowVersionId, + VariableCollection variables) + { + var time = _clock.GetCurrentInstant(); + + var instance = new WorkflowInstance + { + WorkflowDefinitionId = workflowDefinitionId, + WorkflowVersionId = workflowVersionId, + Variables = variables, + Status = WorkflowStatus.Created, + CreatedAt = time, + InstanceId = await _idGenerator.NextId() + }; + + await _workflowInstanceStore.SaveWorkflowInstanceAsync(instance); + + await _eventPublisher.PublishAsync(new StartWorkflowExecutionCommand(instance.InstanceId)); + + return instance.InstanceId; + } + + public async Task ExecuteWorkflow(long workflowInstanceId) + { + var workflowInstance = await _workflowInstanceStore.GetWorkflowInstanceAsync(workflowInstanceId); + var version = + await _workflowDefinitionStore.GetWorkflowDefinitionVersionAsync(workflowInstance.WorkflowDefinitionId, + workflowInstance.WorkflowVersionId); + + var startActivity = version.ActivityDefinitions.FirstOrDefault(x => x.ActivityName == "START"); + + Guard.Against.Null(startActivity, nameof(startActivity)); + + var firstConnection = + version.Connections.FirstOrDefault(x => x.ActivityId == startActivity.ActivityDefinitionId); + + Guard.Against.Null(firstConnection, nameof(firstConnection)); + + var nextActivity = + version.ActivityDefinitions.FirstOrDefault(x => x.ActivityDefinitionId == firstConnection.TargetActivityId); + + Guard.Against.Null(nextActivity, nameof(nextActivity)); + + var activityDefinition = + await _activityDefinitionStore.GetActivityDefinitionAsync(nextActivity.ActivityDefinitionId); + + await _eventPublisher.PublishAsync(new StartActivityCommand(workflowInstanceId, + activityDefinition.ActivityDefinitionId, workflowInstance.Variables)); + + workflowInstance.Status = WorkflowStatus.Running; + + await _workflowInstanceStore.SaveWorkflowInstanceAsync(workflowInstance); + } + + public async Task ActivityCompleted(ActivityCompletedNotification completedNotification) + { + var workflowInstance = + await _workflowInstanceStore.GetWorkflowInstanceAsync(completedNotification.WorkflowInstanceId); + + var version = await _workflowDefinitionStore.GetWorkflowDefinitionVersionAsync( + workflowInstance.WorkflowDefinitionId, + workflowInstance.WorkflowVersionId); + + workflowInstance.WorkflowLogs.Add(new WorkflowLogEntry(workflowInstance.InstanceId, "Activity completed, {0}", + _clock.GetCurrentInstant())); + + var nextConnection = + version.Connections.FirstOrDefault(x => x.ActivityId == completedNotification.NodeId && x.Outcome == completedNotification.Outcome); + + if (nextConnection == null) + { + await WorkflowCompleted(workflowInstance.InstanceId); + return; + } + + await _eventPublisher.PublishAsync(new StartActivityCommand(workflowInstance.InstanceId, + nextConnection.TargetActivityId, workflowInstance.Variables)); + } + + public Task ActivityFailed(ActivityFailedNotification failedNotification) + { + throw new NotImplementedException(); + } + + public Task RunNextActivity(long workflowInstanceId, long activityDefinitionId) + { + throw new NotImplementedException(); + } + + public async Task WorkflowCompleted(long workflowInstanceId) + { + var workflowInstance = await _workflowInstanceStore.GetWorkflowInstanceAsync(workflowInstanceId); + + workflowInstance.Status = WorkflowStatus.Completed; + workflowInstance.CompletedAt = _clock.GetCurrentInstant(); + + await _workflowInstanceStore.SaveWorkflowInstanceAsync(workflowInstance, true); + + await _eventPublisher.PublishAsync(new WorkflowCompletedNotification(workflowInstanceId)); + } +} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Worker/AssemblyInfo.cs b/source/hosting/Coflo.Hosting.Worker/AssemblyInfo.cs new file mode 100644 index 0000000..518e675 --- /dev/null +++ b/source/hosting/Coflo.Hosting.Worker/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Coflo.Hosting.Worker.Tests")] \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Worker/CapControllers/ActivityEventController.cs b/source/hosting/Coflo.Hosting.Worker/CapControllers/ActivityEventController.cs new file mode 100644 index 0000000..aec96e3 --- /dev/null +++ b/source/hosting/Coflo.Hosting.Worker/CapControllers/ActivityEventController.cs @@ -0,0 +1,23 @@ +using Coflo.Abstractions.Activities.Commands; +using DotNetCore.CAP; +using Mediator; +using Microsoft.AspNetCore.Mvc; + +namespace Coflo.Hosting.Worker.CapControllers; + +public class ActivityEventController : ControllerBase +{ + private readonly IMediator _mediator; + + public ActivityEventController(IMediator mediator) + { + _mediator = mediator; + } + + [NonAction] + [CapSubscribe(nameof(StartActivityCommand))] + public async Task StartActivityEvent(StartActivityCommand command, CancellationToken cancellationToken = default!) + { + await _mediator.Publish(command, cancellationToken); + } +} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Worker/Class1.cs b/source/hosting/Coflo.Hosting.Worker/Class1.cs deleted file mode 100644 index bc52155..0000000 --- a/source/hosting/Coflo.Hosting.Worker/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Coflo.Hosting.Worker; - -public class Class1 -{ -} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Worker/Coflo.Hosting.Worker.csproj b/source/hosting/Coflo.Hosting.Worker/Coflo.Hosting.Worker.csproj index 6836c68..a3d1967 100644 --- a/source/hosting/Coflo.Hosting.Worker/Coflo.Hosting.Worker.csproj +++ b/source/hosting/Coflo.Hosting.Worker/Coflo.Hosting.Worker.csproj @@ -6,4 +6,9 @@ enable + + + + + diff --git a/source/hosting/Coflo.Hosting.Worker/Commands/StartActivityCommandHandler.cs b/source/hosting/Coflo.Hosting.Worker/Commands/StartActivityCommandHandler.cs new file mode 100644 index 0000000..b83c455 --- /dev/null +++ b/source/hosting/Coflo.Hosting.Worker/Commands/StartActivityCommandHandler.cs @@ -0,0 +1,113 @@ +using System.Data; +using System.Reflection; +using Ardalis.GuardClauses; +using Coflo.Abstractions.Activities.Attributes; +using Coflo.Abstractions.Activities.Commands; +using Coflo.Abstractions.Activities.Contracts; +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Activities.Stores; +using Coflo.Abstractions.Events; +using Coflo.Abstractions.Variables.Model; +using Coflo.Abstractions.Workflows.Notifications; +using Coflo.Core.Snowflake.Generators; +using Mediator; +using Microsoft.Extensions.DependencyInjection; + +namespace Coflo.Hosting.Worker.Commands; + +public class StartActivityCommandHandler : ICommandHandler +{ + private readonly IActivityDefinitionStore _activityDefinitionStore; + private readonly IIdGenerator _idGenerator; + private readonly IServiceProvider _serviceProvider; + private readonly IEventPublisher _publisher; + + public StartActivityCommandHandler(IActivityDefinitionStore activityDefinitionStore, IIdGenerator idGenerator, + IServiceProvider serviceProvider, IEventPublisher publisher) + { + _activityDefinitionStore = activityDefinitionStore; + _idGenerator = idGenerator; + _serviceProvider = serviceProvider; + _publisher = publisher; + } + + public async ValueTask Handle(StartActivityCommand command, CancellationToken cancellationToken) + { + var activityDef = await _activityDefinitionStore.GetActivityDefinitionAsync(command.ActivityDefinitionId); + + var activityType = Type.GetType(activityDef.ActivityName, true, true); + + Guard.Against.Null(activityType); + + var activity = (IActivity)_serviceProvider.GetRequiredService(activityType); + + var executionContext = new ActivityExecutionContext(command.VariableCollection, command.WorkflowInstanceId, + await _idGenerator.NextId(), activityDef.ActivityName); + + PopulateInputVariablesToProperties(activityType, activity, activityDef, command.VariableCollection); + PopulateOutputVariablesToProperties(activityType, activity, activityDef, command.VariableCollection); + + var result = await activity.ExecuteAsync(executionContext); + + await _publisher.PublishAsync(new ActivityCompletedNotification + { + Variables = result.VariableCollection, + WorkflowInstanceId = command.WorkflowInstanceId, + Outcome = result.Outcome, + NodeId = command.ActivityDefinitionId + }); + + return Unit.Value; + } + + internal void PopulateInputVariablesToProperties(Type activityType, IActivity activity, + ActivityDefinition activityDefinition, + IVariableCollection variables) + { + var properties = activityType.GetProperties(); + + foreach (var property in properties.Where(x => + activityDefinition.InputMappings.Any(y => y.ActivityInputField == x.Name) && + x.GetCustomAttribute() != null)) + { + var input = activityDefinition.InputMappings.FirstOrDefault(x => x.ActivityInputField == property.Name); + + if (input?.VariableDefinition?.Name == null) continue; + + var variable = variables[input.VariableDefinition.Name]; + + if (variable?.Value.GetType() != property.PropertyType) + throw new ConstraintException("Variable type does not match property type"); + + property.SetValue(activity, variable.Value); + } + } + + internal void PopulateOutputVariablesToProperties(Type activityType, IActivity activity, + ActivityDefinition activityDefinition, + IVariableCollection variables) + { + var properties = activityType.GetProperties(); + + foreach (var property in properties) + { + var outputAttribute = property.GetCustomAttribute(); + + if (outputAttribute == null) continue; + var output = activityDefinition.OutputMappings.FirstOrDefault(x => x.ActivityOutputField == property.Name); + + if (string.IsNullOrEmpty(output?.VariableDefinition?.Name)) continue; + + var variable = variables[output.VariableDefinition.Name]; + + if (variable?.GetType() != property.GetType()) + throw new ConstraintException("Variable type does not match property type"); + + var value = property.GetValue(activity); + + variable.SetValue(value); + + variables.AddOrUpdate(variable); + } + } +} \ No newline at end of file diff --git a/source/hosting/Coflo.Hosting.Worker/ServiceCollectionExtensions.cs b/source/hosting/Coflo.Hosting.Worker/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..a90ce8a --- /dev/null +++ b/source/hosting/Coflo.Hosting.Worker/ServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using DotNetCore.CAP; +using Microsoft.Extensions.DependencyInjection; + +namespace Coflo.Hosting.Worker; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddWorker(this IServiceCollection services, Action capOptions) + { + services.AddMediator(); + services.AddCap(capOptions); + + return services; + } +} \ No newline at end of file diff --git a/source/infrastructure/caching/Coflo.Infrastructure.Caching.Redis/Coflo.Infrastructure.Caching.Redis.csproj b/source/infrastructure/caching/Coflo.Infrastructure.Caching.Redis/Coflo.Infrastructure.Caching.Redis.csproj new file mode 100644 index 0000000..3f26510 --- /dev/null +++ b/source/infrastructure/caching/Coflo.Infrastructure.Caching.Redis/Coflo.Infrastructure.Caching.Redis.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + diff --git a/source/infrastructure/caching/Coflo.Infrastructure.Caching.Redis/RedisCacheProvider.cs b/source/infrastructure/caching/Coflo.Infrastructure.Caching.Redis/RedisCacheProvider.cs new file mode 100644 index 0000000..c0aafc1 --- /dev/null +++ b/source/infrastructure/caching/Coflo.Infrastructure.Caching.Redis/RedisCacheProvider.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Caching.Contracts; +using Coflo.Abstractions.Workflows.Models; +using StackExchange.Redis; + +namespace Coflo.Infrastructure.Caching.Redis; + +public class RedisCacheProvider : ICacheProvider +{ + private readonly IConnectionMultiplexer _connectionMultiplexer; + private readonly IDatabase _database; + + private static JsonSerializerOptions _jsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + public RedisCacheProvider(IConnectionMultiplexer connectionMultiplexer) + { + _connectionMultiplexer = connectionMultiplexer; + _database = _connectionMultiplexer.GetDatabase(); + } + + public Task Insert(string key, T value) + { + return _database.SetAddAsync(new RedisKey(key), + new RedisValue(JsonSerializer.Serialize(value, _jsonSerializerOptions))); + } + + public async ValueTask Get(string key) + { + var redisValue = await _database.StringGetAsync(new RedisKey(key)); + return redisValue.IsNullOrEmpty + ? default + : JsonSerializer.Deserialize(redisValue, _jsonSerializerOptions); + } + + public async ValueTask Exists(string key) + { + return await _database.KeyExistsAsync(new RedisKey(key)); + } + + public Task Delete(string key) + { + return _database.KeyDeleteAsync(new RedisKey(key)); + } +} \ No newline at end of file diff --git a/source/infrastructure/messaging/Coflo.Infrastructure.Messaging.RabbitMQ/Class1.cs b/source/infrastructure/messaging/Coflo.Infrastructure.Messaging.RabbitMQ/Class1.cs deleted file mode 100644 index 46849ce..0000000 --- a/source/infrastructure/messaging/Coflo.Infrastructure.Messaging.RabbitMQ/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Coflo.Infrastructure.Messaging.RabbitMQ; - -public class Class1 -{ -} \ No newline at end of file diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistance.MySQL/Class1.cs b/source/infrastructure/persistance/Coflo.Infrastructure.Persistance.MySQL/Class1.cs deleted file mode 100644 index 0f39bc7..0000000 --- a/source/infrastructure/persistance/Coflo.Infrastructure.Persistance.MySQL/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Coflo.Infrastructure.Persistance.MySQL; - -public class Class1 -{ -} \ No newline at end of file diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistance.MySQL/Coflo.Infrastructure.Persistance.MySQL.csproj b/source/infrastructure/persistance/Coflo.Infrastructure.Persistance.MySQL/Coflo.Infrastructure.Persistance.MySQL.csproj deleted file mode 100644 index 6836c68..0000000 --- a/source/infrastructure/persistance/Coflo.Infrastructure.Persistance.MySQL/Coflo.Infrastructure.Persistance.MySQL.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net7.0 - enable - enable - - - diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/CassandraMappings/WorkflowDefinitionMapping.cs b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/CassandraMappings/WorkflowDefinitionMapping.cs new file mode 100644 index 0000000..e6e4b93 --- /dev/null +++ b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/CassandraMappings/WorkflowDefinitionMapping.cs @@ -0,0 +1,14 @@ +using Cassandra.Mapping; +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Infrastructure.Persistance.Cassandra.CassandraMappings; + +public class WorkflowDefinitionMapping : Mappings +{ + public WorkflowDefinitionMapping() + { + For() + .PartitionKey(x => x.TenantId) + .Column(x => x.Versions, map => map.Ignore()); + } +} \ No newline at end of file diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/CassandraMappings/WorkflowDefinitionVersionMapping.cs b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/CassandraMappings/WorkflowDefinitionVersionMapping.cs new file mode 100644 index 0000000..e9c8d49 --- /dev/null +++ b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/CassandraMappings/WorkflowDefinitionVersionMapping.cs @@ -0,0 +1,15 @@ +using Cassandra.Mapping; +using Coflo.Abstractions.Workflows.Models; + +namespace Coflo.Infrastructure.Persistance.Cassandra.CassandraMappings; + +public class WorkflowDefinitionVersionMapping : Mappings +{ + public WorkflowDefinitionVersionMapping() + { + For() + .TableName("workflow_definition_version") + .PartitionKey(x => x.TenantId) + .ClusteringKey(x => x.WorkflowDefinitionId); + } +} \ No newline at end of file diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Coflo.Infrastructure.Persistence.Cassandra.csproj b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Coflo.Infrastructure.Persistence.Cassandra.csproj new file mode 100644 index 0000000..27dbc32 --- /dev/null +++ b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Coflo.Infrastructure.Persistence.Cassandra.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + Coflo.Infrastructure.Persistance.Cassandra + + + + + + + + + + + + + ICassandraSessionFactory.cs + + + + diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Factories/CassandraSessionFactory.cs b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Factories/CassandraSessionFactory.cs new file mode 100644 index 0000000..dcdb97b --- /dev/null +++ b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Factories/CassandraSessionFactory.cs @@ -0,0 +1,23 @@ +using Cassandra; + +namespace Coflo.Infrastructure.Persistance.Cassandra.Factories; + +internal class CassandraSessionFactory : ICassandraSessionFactory +{ + private readonly ICluster _cluster; + + public CassandraSessionFactory(ICluster cluster) + { + _cluster = cluster; + } + + public Task GetSessionAsync() + { + return _cluster.ConnectAsync(); + } + + public ISession GetSession() + { + return _cluster.Connect(); + } +} \ No newline at end of file diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Factories/ICassandraSessionFactory.cs b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Factories/ICassandraSessionFactory.cs new file mode 100644 index 0000000..ecc8c67 --- /dev/null +++ b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Factories/ICassandraSessionFactory.cs @@ -0,0 +1,9 @@ +using Cassandra; + +namespace Coflo.Infrastructure.Persistance.Cassandra.Factories; + +internal interface ICassandraSessionFactory +{ + Task GetSessionAsync(); + ISession GetSession(); +} \ No newline at end of file diff --git a/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Repositories/WorkflowDefinitionRepository.cs b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Repositories/WorkflowDefinitionRepository.cs new file mode 100644 index 0000000..d9d994c --- /dev/null +++ b/source/infrastructure/persistance/Coflo.Infrastructure.Persistence.Cassandra/Repositories/WorkflowDefinitionRepository.cs @@ -0,0 +1,85 @@ +using Cassandra; +using Cassandra.Data.Linq; +using Coflo.Abstractions.Workflows.Models; +using Coflo.Infrastructure.Persistance.Cassandra.Factories; + +namespace Coflo.Infrastructure.Persistance.Cassandra.Repositories; + +internal class CassandraWorkflowDefinitionRepository : IWorkflowDefinitionRepository, IDisposable +{ + private readonly ISession _session; + private readonly Table _workflowDefinitionVersions; + private readonly Table _workflowDefinitions; + + public CassandraWorkflowDefinitionRepository(ICassandraSessionFactory cassandraSessionFactory) + { + _session = cassandraSessionFactory.GetSession(); + _workflowDefinitionVersions = _session.GetTable(); + _workflowDefinitions = _session.GetTable(); + } + + public async ValueTask GetWorkflowDefinition(long workflowDefinitionId) + { + var result = await _workflowDefinitions + .FirstOrDefault(x => x.WorkflowDefinitionId == workflowDefinitionId) + .ExecuteAsync(); + + return result; + } + + public async ValueTask GetWorkflowDefinitionVersion(long workflowDefinitionId, + long workflowVersionId) + { + var result = await _workflowDefinitionVersions + .FirstOrDefault(x => + x.WorkflowDefinitionId == workflowDefinitionId && x.WorkflowVersionId == workflowVersionId) + .ExecuteAsync(); + + return result; + } + + public async Task UpdateWorkflowDefinition(WorkflowDefinition workflowDefinition) + { + var workflowDef = + await _workflowDefinitions + .FirstOrDefault(x => x.WorkflowDefinitionId == workflowDefinition.WorkflowDefinitionId) + .ExecuteAsync(); + + if (workflowDef is null) + { + await InsertWorkflowDefinition(workflowDefinition); + + return; + } + + workflowDef.Name = workflowDefinition.Name; + + var t = _workflowDefinitions + .UpdateIf(x=> x.WorkflowDefinitionId == workflowDefinition.WorkflowDefinitionId); + } + + public async Task InsertWorkflowDefinition(WorkflowDefinition workflowDefinition) + { + await _workflowDefinitions + .Insert(workflowDefinition) + .ExecuteAsync(); + } + + public async Task InsertWorkflowDefinitionVersion(WorkflowDefinitionVersion workflowDefinitionVersion) + { + await _workflowDefinitionVersions + .Insert(workflowDefinitionVersion) + .ExecuteAsync(); + } + + public async Task RemoveWorkflowDefinition(long workflowDefinitionId) + { + await _workflowDefinitions.DeleteIf(x => x.WorkflowDefinitionId == workflowDefinitionId).ExecuteAsync(); + await _workflowDefinitionVersions.DeleteIf(x => x.WorkflowDefinitionId == workflowDefinitionId).ExecuteAsync(); + } + + public void Dispose() + { + _session.Dispose(); + } +} \ No newline at end of file diff --git a/tests/core/Coflo.Core.Snowflake.Tests/Coflo.Core.Snowflake.Tests.csproj b/tests/core/Coflo.Core.Snowflake.Tests/Coflo.Core.Snowflake.Tests.csproj new file mode 100644 index 0000000..0541b78 --- /dev/null +++ b/tests/core/Coflo.Core.Snowflake.Tests/Coflo.Core.Snowflake.Tests.csproj @@ -0,0 +1,32 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/core/Coflo.Core.Snowflake.Tests/IdGeneratorTests.cs b/tests/core/Coflo.Core.Snowflake.Tests/IdGeneratorTests.cs new file mode 100644 index 0000000..062be60 --- /dev/null +++ b/tests/core/Coflo.Core.Snowflake.Tests/IdGeneratorTests.cs @@ -0,0 +1,87 @@ +using System.Globalization; +using Coflo.Core.Snowflake.Generators; +using FluentAssertions; +using FluentAssertions.Extensions; +using Microsoft.Extensions.Configuration; +using NodaTime; +using NodaTime.Testing; +using Xunit.Abstractions; + +namespace Coflo.Core.Snowflake.Tests; + +public class IdGeneratorTests +{ + private readonly DateTimeFormatInfo _dateTimeFormat; + private readonly FakeClock _fakeClock; + private readonly IdGenerator _idGenerator; + private readonly ITestOutputHelper _testOutputHelper; + + public IdGeneratorTests(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + _dateTimeFormat = new CultureInfo("en-GB").DateTimeFormat; + _fakeClock = + new FakeClock(Instant.FromDateTimeUtc(DateTime.Parse("14/04/2023 00:00:00", _dateTimeFormat).AsUtc())); + + var inMemorySettings = new Dictionary + { + { "MachineId", "1" } + }; + + var mockConfiguration = new ConfigurationBuilder() + .AddInMemoryCollection(inMemorySettings) + .Build(); + + _idGenerator = new IdGenerator(_fakeClock, mockConfiguration); + } + + [Fact] + public async Task Assert_NextId_Returns_Correct_Id() + { + var result = await _idGenerator.NextId(); + + var decodedId = IdGenerator.DecodeId(result); + + decodedId.MachineId.Should().Be(1); + decodedId.Sequence.Should().Be(0); + decodedId.Timestamp.Should().Be(_fakeClock.GetCurrentInstant()); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + public async Task Assert_NextId_Returns_Correct_Id_When_Sequence_Overflows(int sequence) + { + var expectedInstant = + Instant.FromDateTimeUtc(DateTime.Parse("14/04/2023 00:00:00", _dateTimeFormat).AsUtc() + + TimeSpan.FromSeconds(sequence)); + _fakeClock.Reset(expectedInstant); + var result = await _idGenerator.NextId(); + + var decodedId = IdGenerator.DecodeId(result); + _testOutputHelper.WriteLine(decodedId.Id.ToString()); + decodedId.MachineId.Should().Be(1); + decodedId.Sequence.Should().Be(0); + decodedId.Timestamp.Should().Be(expectedInstant); + } + + [Fact] + public async Task Assert_NextId_Returns_Correct_Id_When_Sequence_Overflows_Then_Resets() + { + var expectedInstant = + Instant.FromDateTimeUtc(DateTime.Parse("14/04/2023 00:00:00", _dateTimeFormat).AsUtc() + + TimeSpan.FromSeconds(5)); + _fakeClock.Reset(expectedInstant); + var result = await _idGenerator.NextId(); + + var decodedId = IdGenerator.DecodeId(result); + _testOutputHelper.WriteLine(decodedId.Id.ToString()); + decodedId.MachineId.Should().Be(1); + decodedId.Sequence.Should().Be(0); + decodedId.Timestamp.Should().Be(expectedInstant); + } +} \ No newline at end of file diff --git a/tests/core/Coflo.Core.Snowflake.Tests/Usings.cs b/tests/core/Coflo.Core.Snowflake.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/core/Coflo.Core.Snowflake.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/tests/hosting/Coflo.Hosting.Worker.Tests/Coflo.Hosting.Worker.Tests.csproj b/tests/hosting/Coflo.Hosting.Worker.Tests/Coflo.Hosting.Worker.Tests.csproj new file mode 100644 index 0000000..79492d3 --- /dev/null +++ b/tests/hosting/Coflo.Hosting.Worker.Tests/Coflo.Hosting.Worker.Tests.csproj @@ -0,0 +1,30 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/hosting/Coflo.Hosting.Worker.Tests/Commands/StartActivityCommandHandlerTests.cs b/tests/hosting/Coflo.Hosting.Worker.Tests/Commands/StartActivityCommandHandlerTests.cs new file mode 100644 index 0000000..cc8f0ec --- /dev/null +++ b/tests/hosting/Coflo.Hosting.Worker.Tests/Commands/StartActivityCommandHandlerTests.cs @@ -0,0 +1,115 @@ +using System.Data; +using Coflo.Abstractions.Activities.Commands; +using Coflo.Abstractions.Activities.Models; +using Coflo.Abstractions.Activities.Stores; +using Coflo.Abstractions.Evaluation.Contracts; +using Coflo.Abstractions.Events; +using Coflo.Abstractions.Variables.Enums; +using Coflo.Abstractions.Variables.Model; +using Coflo.Activities.Primitives.Control; +using Coflo.Core.Snowflake.Generators; +using Coflo.Hosting.Worker.Commands; +using FluentAssertions; +using Moq; + +namespace Coflo.Hosting.Worker.Tests.Commands; + +public class StartActivityCommandHandlerTests +{ + public StartActivityCommandHandlerTests() + { + } + + [Fact] + public async Task Input_Mapping_Maps_To_Activity_Properties() + { + // Arrange + var mockActivityDefinitionStore = new Mock(); + var mockIdGenerator = new Mock(); + var mockServiceProvider = new Mock(); + var mockEventPublisher = new Mock(); + var mockEvaluator = new Mock(); + + mockEvaluator.Setup(x => x.EvaluateAsync(It.IsAny())) + .ReturnsAsync(() => true); + + var command = new StartActivityCommandHandler(mockActivityDefinitionStore.Object, mockIdGenerator.Object, + mockServiceProvider.Object, mockEventPublisher.Object); + + var variableDef = new VariableDefinition("test", VariableType.String, false, "TEST"); + var variable = new VariableInstance(variableDef); + variable.SetValue("TEST2"); + + var variableCollection = new VariableCollection(); + + variableCollection.AddOrUpdate(variable); + + var ifActivity = new If(mockEvaluator.Object); + var activityDef = new ActivityDefinition + { + DisplayName = "Test", + ActivityName = "IF", + InputMappings = new List() + { + new ActivityInputMapping() + { + VariableDefinition = variableDef, + ActivityInputField = nameof(If.Condition) + } + } + }; + + // Act + + command.PopulateInputVariablesToProperties(typeof(If), ifActivity, activityDef, variableCollection); + + // Assert + ifActivity.Condition.Should().Be(variable.Value as string); + } + + [Fact] + public async Task Input_Mapping_Throws_Constraint_Exception() + { + // Arrange + var mockActivityDefinitionStore = new Mock(); + var mockIdGenerator = new Mock(); + var mockServiceProvider = new Mock(); + var mockEventPublisher = new Mock(); + var mockEvaluator = new Mock(); + + mockEvaluator.Setup(x => x.EvaluateAsync(It.IsAny())) + .ReturnsAsync(() => true); + + var command = new StartActivityCommandHandler(mockActivityDefinitionStore.Object, mockIdGenerator.Object, + mockServiceProvider.Object, mockEventPublisher.Object); + + var variableDef = new VariableDefinition("test", VariableType.Boolean, false, false); + var variable = new VariableInstance(variableDef); + variable.SetValue(false); + + var variableCollection = new VariableCollection(); + + variableCollection.AddOrUpdate(variable); + + var ifActivity = new If(mockEvaluator.Object); + var activityDef = new ActivityDefinition + { + DisplayName = "Test", + ActivityName = "IF", + InputMappings = new List() + { + new ActivityInputMapping() + { + VariableDefinition = variableDef, + ActivityInputField = nameof(If.Condition) + } + } + }; + + // Act + var act = () => command.PopulateInputVariablesToProperties(typeof(If), ifActivity, activityDef, variableCollection); + + // Assert + act.Should().ThrowExactly(); + } +} \ No newline at end of file diff --git a/tests/hosting/Coflo.Hosting.Worker.Tests/Usings.cs b/tests/hosting/Coflo.Hosting.Worker.Tests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/tests/hosting/Coflo.Hosting.Worker.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file