-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
130 lines (120 loc) · 5.38 KB
/
Program.cs
File metadata and controls
130 lines (120 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using Flowthru.Core.Cli;
using Flowthru.Core.Data.Validation;
using Flowthru.Core.Effects;
using Flowthru.Core.Services;
using Flowthru.Meta;
using Flowthru.Meta.Providers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SimpleEffectsExample.Data;
using SimpleEffectsExample.Flows.Reporting;
using SimpleEffectsExample.Services;
namespace SimpleEffectsExample;
/// <summary>
/// Entry point for the <c>SimpleEffectsExample</c> starter example. Demonstrates the
/// minimal ceremony needed to wire a service-bearing step into a Flowthru flow
/// with full preflight inspection and metadata emission.
/// </summary>
/// <remarks>
/// <para>
/// The pattern shown here is the recommended starting point for any external-system
/// integration: register the service, attach <c>AddFlowthruInspect<TService></c>
/// for preflight reachability, and inject the service into the step's
/// <c>Create(...)</c> factory. No Flowthru-specific extension package is required —
/// the user's own service implementation drops in.
/// </para>
/// </remarks>
public class Program
{
public static Task<int> Main(string[] args) =>
FlowthruCli.RunStandaloneAsync(
args,
services => ConfigureServices(services, Directory.GetCurrentDirectory())
);
/// <summary>
/// Builds a configured service provider. Used by integration tests in
/// <c>Flowthru.Tests.Examples</c> to run the example end-to-end without going
/// through the CLI entry point.
/// </summary>
public static IServiceProvider ConfigureServices(string? basePath = null)
{
var services = new ServiceCollection();
ConfigureServices(services, basePath ?? Directory.GetCurrentDirectory());
return services.BuildServiceProvider();
}
private static void ConfigureServices(IServiceCollection services, string basePath)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(basePath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: false)
.Build();
// ── External service registration ─────────────────────────────────────
// TimeApiClient owns its own HttpClient internally — see Services/TimeApiClient.cs.
// Production projects can swap this for the typed-client pattern via
// Microsoft.Extensions.Http (services.AddHttpClient<IRemoteTimeService, …>())
// when they want IHttpClientFactory's connection-pool semantics. The example
// uses the simpler pattern to avoid an extra DI package dependency.
services.AddSingleton<IRemoteTimeService, TimeApiClient>();
// ── Pre-flight inspection sidecar ─────────────────────────────────────
// Attaches reachability validation to IRemoteTimeService without modifying
// the service contract. Flowthru's preflight loop runs this before any step
// executes and fails fast if the upstream is unreachable. The probe is
// wrapped in FlowIO.LiftAsync — the standard "lift an async operation into
// FlowIO" helper.
services.AddFlowthruInspect<IRemoteTimeService>((svc, ct) =>
FlowIO.LiftAsync<ValidationResult>(async cancel =>
{
// TimeApiClient exposes a PingAsync helper; in general a sidecar can
// call any method on the service that's cheap and safe to retry.
if (svc is TimeApiClient apiClient)
{
return await apiClient.PingAsync(cancel)
? ValidationResult.Success()
: ValidationResult.Failure(
"RemoteTime",
ValidationErrorType.NotFound,
"timeapi.io is unreachable. Check internet access or service status."
);
}
// Fake/test implementations registered via [FUnitStubContainer] are
// always considered reachable; tests don't exercise the sidecar.
return ValidationResult.Success();
})
);
services.AddFlowthru(
configuration,
flowthru =>
{
flowthru.RegisterCatalog(_ => new Catalog(Path.Combine(basePath, "Data")));
flowthru.ConfigureMetadata(meta =>
{
var metadataPath = Path.Combine(basePath, "Metadata");
meta
.AddProvider<JsonMetadataProvider, JsonMetadataProviderBuilder>(json =>
json.WithOutputDirectory(metadataPath)
)
.AddProvider<MermaidMetadataProvider, MermaidMetadataProviderBuilder>(mermaid =>
mermaid.WithOutputDirectory(metadataPath)
);
});
// RegisterFlow(label, Delegate) inspects the delegate's parameter types and
// resolves each one from DI: 'Catalog' (subclass of CatalogAbstract) is
// resolved as the registered catalog; 'IRemoteTimeService' is resolved as
// the typed HttpClient binding above.
flowthru
.RegisterFlow(label: "ReportTime", flow: ReportTimeFlow.Create)
.WithDescription(
"Fetches the current UTC time from a public service and writes a "
+ "formatted report. Demonstrates the effect-as-step pattern."
);
}
);
services.AddLogging(logging =>
{
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Information);
});
}
}