diff --git a/temporal-examples/LICENSE.md b/LICENSE.md similarity index 100% rename from temporal-examples/LICENSE.md rename to LICENSE.md diff --git a/README.md b/README.md index 3e666e4..6e77d34 100644 --- a/README.md +++ b/README.md @@ -2,36 +2,52 @@ ## Purpose of this Repo -This is to show several features of temporal in a way that is easy to understand and run. All you need is docker as everything will run in docker containers. +This is to show several features of Temporal in a way that is easy to understand and run. All you +need is Docker as everything will run in Docker containers. -It will run a swagger page that allows you to start workflows using a rest api. +It will run a [Swagger page](http://localhost:8080/swagger/index.html) that allows you to start +workflows and send signals to paused workflows via a REST API. ## Running the project - you'll need Docker -Make sure your terminal is in the temporal-examples folder and then run docker compose up. This should also run the temporal workers as well. If you ever need to debug the temporal workflows you can debug via a docker container by installing the "Container Tools" extension in VS Code. There is also an option to do the debugging through Visual Studio tooling as well. +Make sure your terminal is in the temporal-examples folder and then run `docker compose up`. This +will start all services. If you ever need to debug the Temporal workflows you can debug via a Docker +container by installing the "Container Tools" extension in VS Code. There is also an option to do +the debugging through Visual Studio tooling as well. -This set of docker containers actually installs the temporal cli to a container. If you want to execute commands using it you should either exec into it or access exec in Docker Desktop. The container is named temporal-cli. If you want examples of this go to the temporal-examples/scripts folder. There is also the documentation here: https://docs.temporal.io/cli#command-set +This set of Docker containers actually installs the Temporal CLI to an isolated. If you want to +execute commands using it you should either exec into it or access exec in Docker Desktop. The +container is named `temporal-cli`. If you want examples of this go to the `temporal-examples/scripts` +folder. There is also the documentation [here](https://docs.temporal.io/cli#command-set) -## The Examples +## Examples -We have several different examples of what Temporal can do in the Workflows files which are in the workflows project. +We have several different examples of what Temporal can do in the Workflows files which are in the +Workflows project. -**Examples.workflow.cs**- shows a default Temporal workflow with a generic activity +**Examples.workflow.cs** - shows a default Temporal workflow with a generic activity -**ExampleWithChildren.workflow.cs** - shows you how you can compose workflows together. This creates 1-10 child workflows and waits for them to finish. +**ExampleWithChildren.workflow.cs** - shows you how you can compose workflows together. This creates +1-10 child workflows and waits for them to finish. -**WaitingSignal.workflow.cs** - shows how you can pause a workflow indefinitely or with a timeout to wait for a "signal". This signal in our example is caused by a rest api. While paused the workflow takes up very few resources. The SignalController inside the rest-controller project is where we send the signal. THis also shows how you can search the Temporal Server for specific workflows. In this case we are using the Workflow ID. +**WaitingSignal.workflow.cs** - shows how you can pause a workflow indefinitely or with a timeout to +wait for a "signal". This signal in our example is caused by a REST API. While paused the workflow +takes up very few resources. The SignalController inside the rest-controller project is where we send +the signal. This also shows how you can search the Temporal Server for specific workflows. In this +case we are using the Workflow ID. ## Monitoring and Metrics -The docker compose file contains [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/) services. -Both the Temporal service and all worker services expose metrics to Prometheus, which are collected based on the -settings in `prometheus.yml`. Prometheus can be manually added to Grafana as a data source. +The docker compose file contains [Prometheus](https://prometheus.io/) and +[Grafana](https://grafana.com/) services. Both the Temporal service and all worker services expose +metrics to Prometheus, which are collected based on the settings in `prometheus.yml`. Prometheus can +be manually added to Grafana as a data source. -The Prometheus UI can be accessed at [query page](http://localhost:9090/query), and details of the different scraped metrics -endpoints can be seen at [targets page](http://localhost:9090/targets). +The Prometheus UI can be accessed at [query page](http://localhost:9090/query), and details of the +different scraped metrics endpoints can be seen at [targets page](http://localhost:9090/targets). -The Grafana UI can be accessed at [Grafana login](http://localhost:3000/login) - Login details are admin/admin. +The Grafana UI can be accessed at [Grafana login](http://localhost:3000/login) - Login details are +admin/admin. ## License diff --git a/temporal-examples/Directory.Packages.props b/temporal-examples/Directory.Packages.props index c443418..578f3ae 100644 --- a/temporal-examples/Directory.Packages.props +++ b/temporal-examples/Directory.Packages.props @@ -1,6 +1,7 @@ - + true @@ -10,6 +11,10 @@ + + + + diff --git a/temporal-examples/RestController/MicroserviceController.cs b/temporal-examples/RestController/MicroserviceController.cs index fb10ffd..f100879 100644 --- a/temporal-examples/RestController/MicroserviceController.cs +++ b/temporal-examples/RestController/MicroserviceController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Temporalio.Client; -using workflows; +using Workflows; namespace RestController { diff --git a/temporal-examples/RestController/SignalController.cs b/temporal-examples/RestController/SignalController.cs index 22bf941..434e2b5 100644 --- a/temporal-examples/RestController/SignalController.cs +++ b/temporal-examples/RestController/SignalController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Temporalio.Client; -using workflows; +using Workflows; namespace RestController { diff --git a/temporal-examples/RestController/appsettings.Development.json b/temporal-examples/RestController/appsettings.Development.json index 0c208ae..958ff5a 100644 --- a/temporal-examples/RestController/appsettings.Development.json +++ b/temporal-examples/RestController/appsettings.Development.json @@ -1,8 +1,13 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Temporal": { + "ClientTargetHost": "host.docker.internal:7233", + "Namespace": "default", + "TaskQueue": "example" } - } } diff --git a/temporal-examples/Shared/AutoFac/Modules/LoggingModule.cs b/temporal-examples/Shared/AutoFac/Modules/LoggingModule.cs index 4b1a1ce..c2f99cd 100644 --- a/temporal-examples/Shared/AutoFac/Modules/LoggingModule.cs +++ b/temporal-examples/Shared/AutoFac/Modules/LoggingModule.cs @@ -1,5 +1,11 @@ using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Extensions.Logging; +using Serilog.Formatting.Json; namespace Shared.AutoFac.Modules { @@ -8,48 +14,39 @@ namespace Shared.AutoFac.Modules /// public class LoggingModule : Module { - private readonly LogLevel _minimumLevel; + private readonly IConfiguration _configuration; - /// - /// Creates a new instance of the logging module with the specified minimum log level - /// - /// Minimum log level to display - public LoggingModule(LogLevel minimumLevel = LogLevel.Information) + public LoggingModule(IConfiguration configuration) { - _minimumLevel = minimumLevel; + _configuration = configuration; } - /// - /// Configure the logging services with Autofac - /// protected override void Load(ContainerBuilder builder) { - builder.RegisterGeneric(typeof(Logger<>)).As(typeof(ILogger<>)).SingleInstance(); + var loggerConfig = new LoggerConfiguration() + .Enrich.WithProperty( + "Environment", + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production" + ) + .WriteTo.Console( + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}" + ) + .WriteTo.File( + new JsonFormatter(renderMessage: true), + "logs/log-.json", + rollingInterval: RollingInterval.Day, + shared: true + ); - // Register factory for creating loggers - builder - .Register(c => - { - var factory = LoggerFactory.Create(loggingBuilder => - { - loggingBuilder - .SetMinimumLevel(_minimumLevel) - .AddSimpleConsole(options => - { - options.SingleLine = false; - options.TimestampFormat = "[yyyy-MM-dd HH:mm:ss] "; - options.UseUtcTimestamp = true; - }); - // You can add additional providers here as needed - }); - return factory; - }) - .As() - .SingleInstance(); + var logger = loggerConfig.CreateLogger(); + + Log.Logger = logger; + + builder.RegisterInstance(logger).As().SingleInstance(); - // Register a filter to respect minimum level builder - .Register(c => new LoggerFilterOptions { MinLevel = _minimumLevel }) + .Register(c => new SerilogLoggerFactory(logger, true)) + .As() .SingleInstance(); } } diff --git a/temporal-examples/Shared/Shared.csproj b/temporal-examples/Shared/Shared.csproj index 7ea1a47..359b1e3 100644 --- a/temporal-examples/Shared/Shared.csproj +++ b/temporal-examples/Shared/Shared.csproj @@ -6,8 +6,12 @@ + + + + diff --git a/temporal-examples/docker-compose.yml b/temporal-examples/docker-compose.yml index b40196e..e1dba57 100644 --- a/temporal-examples/docker-compose.yml +++ b/temporal-examples/docker-compose.yml @@ -6,6 +6,8 @@ services: dockerfile: temporal-worker/Dockerfile environment: - Temporal__ClientUrl=temporal:7233 + volumes: + - ./logs:/app/logs depends_on: temporal: condition: service_healthy diff --git a/temporal-examples/microservice-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs b/temporal-examples/microservice-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs index 5874f49..1f78316 100644 --- a/temporal-examples/microservice-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs +++ b/temporal-examples/microservice-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Temporalio.Extensions.Hosting; using Temporalio.Runtime; -using workflows; +using Workflows; namespace TemporalWorker.AutoFac.Modules { diff --git a/temporal-examples/microservice-worker/Program.cs b/temporal-examples/microservice-worker/Program.cs index cf375e5..efbcd54 100644 --- a/temporal-examples/microservice-worker/Program.cs +++ b/temporal-examples/microservice-worker/Program.cs @@ -17,7 +17,7 @@ private static async Task Main(string[] args) .ConfigureContainer( (context, builder) => { - builder.RegisterModule(new LoggingModule(LogLevel.Information)); + builder.RegisterModule(new LoggingModule(context.Configuration)); builder.RegisterModule( new TemporalWorkerConfigurationModule(context.Configuration) ); diff --git a/temporal-examples/temporal-examples.sln b/temporal-examples/temporal-examples.sln index bf39fef..5e8b612 100644 --- a/temporal-examples/temporal-examples.sln +++ b/temporal-examples/temporal-examples.sln @@ -13,6 +13,7 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" ProjectSection(SolutionItems) = preProject + ..\LICENSE.md = ..\LICENSE.md ..\README.md = ..\README.md EndProjectSection EndProject diff --git a/temporal-examples/temporal-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs b/temporal-examples/temporal-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs index 5360d47..982410f 100644 --- a/temporal-examples/temporal-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs +++ b/temporal-examples/temporal-worker/AutoFac/Modules/TemporalWorkerConfigurationModule.cs @@ -4,7 +4,8 @@ using Microsoft.Extensions.DependencyInjection; using Temporalio.Extensions.Hosting; using Temporalio.Runtime; -using workflows; +using Workflows; +using Workflows; namespace TemporalWorker.AutoFac.Modules { @@ -14,8 +15,7 @@ public class TemporalWorkerConfigurationModule : Module public TemporalWorkerConfigurationModule(IConfiguration configuration) { - _configuration = - configuration ?? throw new ArgumentNullException(nameof(configuration)); + _configuration = configuration; } protected override void Load(ContainerBuilder builder) diff --git a/temporal-examples/temporal-worker/Program.cs b/temporal-examples/temporal-worker/Program.cs index 254e861..3ead2e0 100644 --- a/temporal-examples/temporal-worker/Program.cs +++ b/temporal-examples/temporal-worker/Program.cs @@ -2,7 +2,6 @@ using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Shared.AutoFac.Modules; using TemporalWorker.AutoFac.Modules; @@ -17,10 +16,10 @@ private static async Task Main(string[] args) .ConfigureContainer( (context, builder) => { - builder.RegisterModule(new LoggingModule(LogLevel.Information)); builder.RegisterModule( new TemporalWorkerConfigurationModule(context.Configuration) ); + builder.RegisterModule(new LoggingModule(context.Configuration)); } ) .Build(); diff --git a/temporal-examples/temporal-worker/temporal-worker.csproj b/temporal-examples/temporal-worker/temporal-worker.csproj index 4a689fc..3019163 100644 --- a/temporal-examples/temporal-worker/temporal-worker.csproj +++ b/temporal-examples/temporal-worker/temporal-worker.csproj @@ -19,6 +19,10 @@ + + + + diff --git a/temporal-examples/workflows/ExampleActivities.cs b/temporal-examples/workflows/ExampleActivities.cs index bc4bedf..8c7c205 100644 --- a/temporal-examples/workflows/ExampleActivities.cs +++ b/temporal-examples/workflows/ExampleActivities.cs @@ -1,39 +1,58 @@ +using Microsoft.Extensions.Logging; using Temporalio.Activities; -namespace workflows; +namespace Workflows; public class ExampleActivities { + private readonly ILogger _logger; + + public ExampleActivities(ILogger logger) + { + _logger = logger; + } + [Activity] - public static async Task GenericTask() + public async Task GenericTask() { - await Task.Delay(2000); + var duration = 2000; + _logger.LogInformation("Starting delay: {Duration}", duration); + await Task.Delay(duration); + _logger.LogInformation("Finished delay"); return $"generic-task-{DateTime.Now}"; } [Activity] - public static List GenerateChildWorkflowsName() + public List GenerateChildWorkflowsName() { int numberOfChildWorkflows = new Random().Next(1, 10); List childWorkflowsInfo = []; for (int index = 0; index < numberOfChildWorkflows; index++) { - childWorkflowsInfo.Add($"child-{index}-workflow-{DateTime.Now}"); + var name = $"child-{index}-workflow-{DateTime.Now}"; + _logger.LogInformation("Creating child workflow {Name}", name); + childWorkflowsInfo.Add(name); } return childWorkflowsInfo; } [Activity] - public static async Task TaskTriggeredBySignal() + public async Task TaskTriggeredBySignal() { - await Task.Delay(2000); + var duration = 2000; + _logger.LogInformation("Starting delay: {Duration}", duration); + await Task.Delay(duration); + _logger.LogInformation("Finished delay"); return $"task-triggered-by-signal-{DateTime.Now}"; } [Activity] - public static async Task TaskTriggeredByTimeout() + public async Task TaskTriggeredByTimeout() { - await Task.Delay(2000); + var duration = 2000; + _logger.LogInformation("Starting delay: {Duration}", duration); + await Task.Delay(duration); + _logger.LogInformation("Finished delay"); return $"task-triggered-by-timeout-{DateTime.Now}"; } } diff --git a/temporal-examples/workflows/ExampleWithChildren.workflow.cs b/temporal-examples/workflows/ExampleWithChildren.workflow.cs index ef387b5..1da24ec 100644 --- a/temporal-examples/workflows/ExampleWithChildren.workflow.cs +++ b/temporal-examples/workflows/ExampleWithChildren.workflow.cs @@ -1,7 +1,7 @@ using Temporalio.Common; using Temporalio.Workflows; -namespace workflows; +namespace Workflows; [Workflow] public class ExampleWithChildrenWorkflow @@ -19,7 +19,7 @@ public async Task RunAsync() }; List childNames = await Workflow.ExecuteActivityAsync( - () => ExampleActivities.GenerateChildWorkflowsName(), + (ExampleActivities a) => a.GenerateChildWorkflowsName(), new ActivityOptions { StartToCloseTimeout = TimeSpan.FromMinutes(5), @@ -34,6 +34,7 @@ await Workflow.ExecuteChildWorkflowAsync( new() { Id = child, TaskQueue = "example" } ); } + return "All child workflows created and run"; } } diff --git a/temporal-examples/workflows/Examples.workflow.cs b/temporal-examples/workflows/Examples.workflow.cs index 90808c4..d5deca0 100644 --- a/temporal-examples/workflows/Examples.workflow.cs +++ b/temporal-examples/workflows/Examples.workflow.cs @@ -1,7 +1,7 @@ using Temporalio.Common; using Temporalio.Workflows; -namespace workflows; +namespace Workflows; [Workflow] public class ExampleWorkflow @@ -19,7 +19,7 @@ public async Task RunAsync() }; string result = await Workflow.ExecuteActivityAsync( - () => ExampleActivities.GenericTask(), + (ExampleActivities a) => a.GenericTask(), new ActivityOptions { StartToCloseTimeout = TimeSpan.FromMinutes(5), diff --git a/temporal-examples/workflows/Microservice.workflow.cs b/temporal-examples/workflows/Microservice.workflow.cs index 1068af8..7c2f11d 100644 --- a/temporal-examples/workflows/Microservice.workflow.cs +++ b/temporal-examples/workflows/Microservice.workflow.cs @@ -1,7 +1,8 @@ using Temporalio.Common; using Temporalio.Workflows; +using Workflows; -namespace workflows; +namespace Workflows; [Workflow] public class MicroservicesWorkflow @@ -21,7 +22,7 @@ public async Task RunAsync() }; await Workflow.ExecuteActivityAsync( - () => ExampleActivities.GenericTask(), + (ExampleActivities a) => a.GenericTask(), new ActivityOptions { StartToCloseTimeout = TimeSpan.FromMinutes(5), diff --git a/temporal-examples/workflows/MicroserviceActivities.cs b/temporal-examples/workflows/MicroserviceActivities.cs index c935f08..8a08c65 100644 --- a/temporal-examples/workflows/MicroserviceActivities.cs +++ b/temporal-examples/workflows/MicroserviceActivities.cs @@ -1,6 +1,6 @@ using Temporalio.Activities; -namespace workflows; +namespace Workflows; // These activities can be calling an external microservice // Arguably these could be added to the generic activities file but this does diff --git a/temporal-examples/workflows/WaitingSignal.workflow.cs b/temporal-examples/workflows/WaitingSignal.workflow.cs index 774c45c..28b20a0 100644 --- a/temporal-examples/workflows/WaitingSignal.workflow.cs +++ b/temporal-examples/workflows/WaitingSignal.workflow.cs @@ -2,7 +2,7 @@ using Temporalio.Common; using Temporalio.Workflows; -namespace workflows; +namespace Workflows; [Workflow] public class WaitingSignalWorkflow @@ -38,14 +38,14 @@ public async Task RunAsync() if (didNotTimeout) { await Workflow.ExecuteActivityAsync( - () => ExampleActivities.TaskTriggeredBySignal(), + (ExampleActivities a) => a.TaskTriggeredBySignal(), activityOptions ); } else { await Workflow.ExecuteActivityAsync( - () => ExampleActivities.TaskTriggeredByTimeout(), + (ExampleActivities a) => a.TaskTriggeredByTimeout(), activityOptions ); }