diff --git a/src/SquidStd.Aws.Abstractions/README.md b/src/SquidStd.Aws.Abstractions/README.md index fe3bb0b..0ba4d03 100644 --- a/src/SquidStd.Aws.Abstractions/README.md +++ b/src/SquidStd.Aws.Abstractions/README.md @@ -33,6 +33,13 @@ client at that endpoint instead of the regional AWS endpoint. |------------------|-----------------------------------------------------------------------------------| | `AwsConfigEntry` | Region, optional credentials and an optional endpoint override (e.g. LocalStack). | +## Related + +- Article: [AWS abstractions](https://tgiachi.github.io/squid-std/articles/aws-abstractions.html) +- [`SquidStd.Secrets.Aws`](https://tgiachi.github.io/squid-std/articles/secrets-aws.html) - AWS Secrets Manager provider using this config +- [`SquidStd.Messaging.Sqs`](https://tgiachi.github.io/squid-std/articles/messaging-sqs.html) - SQS messaging provider using this config +- [`SquidStd.Storage.S3`](https://tgiachi.github.io/squid-std/articles/storage-s3.html) - S3 storage provider using this config + ## License MIT - part of [SquidStd](https://github.com/tgiachi/squid-std). diff --git a/src/SquidStd.Caching/README.md b/src/SquidStd.Caching/README.md index 228d759..796a7ef 100644 --- a/src/SquidStd.Caching/README.md +++ b/src/SquidStd.Caching/README.md @@ -1,8 +1,13 @@

SquidStd.Caching

-In-memory backend for SquidStd.Caching. Provides an `IMemoryCache`-backed `ICacheProvider` with -absolute TTL and eviction, wired to the shared typed `ICacheService` facade - registered with a -single `AddInMemoryCache()` call. Ideal for single-process apps, tests, and local dev. +In-memory backend for SquidStd.Caching. Provides an `IMemoryCache`-backed `ICacheProvider` with absolute +TTL and eviction, wired to the shared typed `ICacheService` facade - registered with a single +`AddInMemoryCache()` call. + +The contract lives in `SquidStd.Caching.Abstractions`, so code written against `ICacheService` moves +unchanged to a distributed cache: use this in-memory provider for single-process apps, tests, and local +dev, then swap in `SquidStd.Caching.Redis` for production. Values are serialized as JSON in both cases, +so cached shapes behave the same across providers. ## Install @@ -12,29 +17,87 @@ dotnet add package SquidStd.Caching ## Usage +Register through the bootstrap - the provider is an `ISquidStdService`, so `StartAsync` / `StopAsync` +manage its lifecycle for you: + ```csharp -using DryIoc; using SquidStd.Caching.Abstractions.Interfaces; using SquidStd.Caching.Extensions; +using SquidStd.Services.Core.Extensions; +using SquidStd.Services.Core.Services.Bootstrap; + +var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp"); +bootstrap.ConfigureServices(c => c.RegisterCoreServices().AddInMemoryCache()); +await bootstrap.StartAsync(); +``` + +### Get / set with TTL + +```csharp +var cache = bootstrap.Resolve(); + +await cache.SetAsync("greeting", "hello", TimeSpan.FromMinutes(5)); +var value = await cache.GetAsync("greeting"); // "hello", or default on a miss + +var exists = await cache.ExistsAsync("greeting"); // true +var removed = await cache.RemoveAsync("greeting"); // true if the key existed +``` + +Omitting the TTL on `SetAsync` falls back to `CacheOptions.DefaultTtl` (no expiry when that is `null`). -var container = new Container(); -container.AddInMemoryCache("memory://localhost?defaultTtlSeconds=300&keyPrefix=app:"); +### Cache-aside with `GetOrSetAsync` -var cache = container.Resolve(); -await cache.SetAsync("user:1", new { Name = "squid" }); -var user = await cache.GetAsync("user:1"); +`GetOrSetAsync` returns the cached value, or runs the factory, stores the result, and returns it on a +miss - the standard cache-aside pattern in one call: + +```csharp +public sealed record UserProfile(string Id, string Name); + +var profile = await cache.GetOrSetAsync( + $"user:{id}", + async ct => await LoadProfileFromDatabaseAsync(id, ct), + TimeSpan.FromMinutes(10) +); ``` +## Configuration + +`AddInMemoryCache` takes an optional `CacheOptions`. Options are code-only - this package registers no +YAML config section. + +```csharp +c.AddInMemoryCache(new CacheOptions +{ + DefaultTtl = TimeSpan.FromMinutes(5), + KeyPrefix = "app:" +}); +``` + +| Property | Default | Purpose | +|--------------|----------------|------------------------------------------------------------------| +| `DefaultTtl` | `null` | TTL applied when a set call passes none; `null` means no expiry. | +| `KeyPrefix` | `string.Empty` | Prefix prepended to every key. | + +A connection-string overload is also available: +`AddInMemoryCache("memory://local?defaultTtlSeconds=300&keyPrefix=app:")`. + +`CacheMetricsProvider` is registered as `ICacheMetrics` / `IMetricProvider` and exposes aggregate hit, +miss, set, and remove counters plus the hit ratio to the metrics collection system. + ## Key types -| Type | Purpose | -|-------------------------------|-----------------------------------------| -| `CacheRegistrationExtensions` | `AddInMemoryCache(...)` registration. | -| `InMemoryCacheProvider` | `IMemoryCache`-backed `ICacheProvider`. | +| Type | Purpose | +|-------------------------------|------------------------------------------------------------| +| `CacheRegistrationExtensions` | `AddInMemoryCache(...)` registration. | +| `InMemoryCacheProvider` | `IMemoryCache`-backed `ICacheProvider`. | +| `ICacheService` | Typed facade from `SquidStd.Caching.Abstractions`: get/set/TTL, `GetOrSetAsync`, exists/remove. | +| `CacheOptions` | Default TTL and key prefix configuration. | ## Related +- Article: [Caching](https://tgiachi.github.io/squid-std/articles/caching.html) - Tutorial: [Caching](https://tgiachi.github.io/squid-std/tutorials/caching.html) +- Production backend: [SquidStd.Caching.Redis](https://tgiachi.github.io/squid-std/articles/caching-redis.html) ## License diff --git a/src/SquidStd.Messaging.RabbitMq/README.md b/src/SquidStd.Messaging.RabbitMq/README.md index 5261cf8..9dc3b48 100644 --- a/src/SquidStd.Messaging.RabbitMq/README.md +++ b/src/SquidStd.Messaging.RabbitMq/README.md @@ -1,9 +1,17 @@

SquidStd.Messaging.RabbitMq

-RabbitMQ transport for SquidStd.Messaging. Implements `IQueueProvider` on top of the RabbitMQ client, -so the same `IMessageQueue` API publishes to and consumes from a real broker. Registered with a single +RabbitMQ transport for SquidStd.Messaging. Implements `IQueueProvider` and `ITopicProvider` on top of the +RabbitMQ client, so the same `IMessageQueue` / `IMessageTopic` API that runs in-memory during tests +publishes to and consumes from a real broker in production - registered with a single `AddRabbitMqMessaging(...)` call. +The provider declares durable **quorum queues** and publishes persistent messages, so queued work survives +broker restarts. Each queue gets a companion dead-letter queue (`.dlq` by default), wired via +`x-delivery-limit` so the broker itself moves a message there after `MaxDeliveryAttempts` failed +deliveries. Connections are created with automatic recovery enabled, and consumers apply the configured +prefetch via basic QoS. Topics map to fan-out exchanges with an exclusive, auto-delete queue per +subscriber - topic delivery is live fan-out, not durable. + ## Install ```bash @@ -12,29 +20,84 @@ dotnet add package SquidStd.Messaging.RabbitMq ## Usage +Register through the bootstrap - the providers are `ISquidStdService`s, so `bootstrap.StartAsync()` opens +the broker connection and `StopAsync` closes it: + ```csharp -using DryIoc; using SquidStd.Messaging.Abstractions.Interfaces; +using SquidStd.Messaging.RabbitMq.Data.Config; using SquidStd.Messaging.RabbitMq.Extensions; +using SquidStd.Services.Core.Extensions; +using SquidStd.Services.Core.Services.Bootstrap; + +var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp"); + +bootstrap.ConfigureServices(c => c.RegisterCoreServices().AddRabbitMqMessaging(new RabbitMqOptions +{ + HostName = "rabbit.internal", + UserName = "app", + Password = "secret" +})); + +await bootstrap.StartAsync(); +``` + +Publishing and consuming are identical to the in-memory provider - the contracts come from +`SquidStd.Messaging.Abstractions`: -var container = new Container(); -container.AddRabbitMqMessaging("rabbitmq://guest:guest@localhost:5672/"); +```csharp +var queue = bootstrap.Resolve(); + +using (queue.Subscribe("orders", new OrderListener())) // IQueueMessageListenerAsync +{ + await queue.PublishAsync("orders", new OrderPlaced("order-1")); +} -var queue = container.Resolve(); -await queue.PublishAsync("orders", new { Id = 1 }); +var topic = bootstrap.Resolve(); +using var sub = topic.Subscribe("order-events", (order, _) => +{ + Console.WriteLine($"topic saw {order.Id}"); + return Task.CompletedTask; +}); ``` +## Configuration + +`AddRabbitMqMessaging(RabbitMqOptions options, MessagingOptions? messagingOptions = null)` takes the +connection settings plus the shared messaging options (delivery attempts, dead-letter suffix). Options are +code-only - this package registers no YAML config section. + +| `RabbitMqOptions` property | Default | Purpose | +|----------------------------|---------------|-----------------------------------------------------| +| `HostName` | `"localhost"` | Broker host. | +| `Port` | `5672` | Broker port. | +| `VirtualHost` | `"/"` | Virtual host. | +| `UserName` | `"guest"` | User name. | +| `Password` | `"guest"` | Password. | +| `Uri` | `null` | AMQP URI; when set it overrides the fields above. | +| `PrefetchCount` | `10` | Consumer prefetch (basic QoS) per subscriber. | + +A connection-string overload is also available: +`AddRabbitMqMessaging("rabbitmq://app:secret@rabbit.internal:5672/myvhost?prefetch=20&maxDeliveryAttempts=5")`. + +`MessagingMetricsProvider` is registered as `IMessagingMetrics` / `IMetricProvider`; the provider reports +published, delivered, and failed counts plus subscriber counts per queue. + ## Key types -| Type | Purpose | -|-------------------------------------------|-------------------------------------------| -| `RabbitMqMessagingRegistrationExtensions` | `AddRabbitMqMessaging(...)` registration. | -| `RabbitMqQueueProvider` | RabbitMQ-backed `IQueueProvider`. | -| `RabbitMqOptions` | Connection + prefetch configuration. | +| Type | Purpose | +|-------------------------------------------|----------------------------------------------------------| +| `RabbitMqMessagingRegistrationExtensions` | `AddRabbitMqMessaging(...)` registration. | +| `RabbitMqQueueProvider` | Quorum-queue `IQueueProvider` with broker-side DLQ. | +| `RabbitMqTopicProvider` | Fan-out exchange `ITopicProvider`. | +| `RabbitMqOptions` | Connection + prefetch configuration. | ## Related +- Article: [Messaging with RabbitMQ](https://tgiachi.github.io/squid-std/articles/messaging-rabbitmq.html) +- Article: [Messaging](https://tgiachi.github.io/squid-std/articles/messaging.html) - Tutorial: [Messaging](https://tgiachi.github.io/squid-std/tutorials/messaging.html) +- Siblings: [SquidStd.Messaging (in-memory)](https://tgiachi.github.io/squid-std/articles/messaging.html), [SquidStd.Messaging.Sqs](https://tgiachi.github.io/squid-std/articles/messaging-sqs.html) ## License diff --git a/src/SquidStd.Messaging/README.md b/src/SquidStd.Messaging/README.md index e723e09..9d99eb7 100644 --- a/src/SquidStd.Messaging/README.md +++ b/src/SquidStd.Messaging/README.md @@ -1,8 +1,15 @@

SquidStd.Messaging

-In-memory transport for SquidStd.Messaging. Provides a channel-backed `IQueueProvider` with per-queue -buffering, round-robin delivery to subscribers, retry/dead-letter handling, and metrics - registered -with a single `AddInMemoryMessaging()` call. Ideal for single-process apps, tests, and local dev. +In-memory transport for SquidStd.Messaging. Provides channel-backed `IQueueProvider` and `ITopicProvider` +implementations behind the shared `IMessageQueue` / `IMessageTopic` facades: per-queue buffering, +round-robin (competing-consumers) delivery, retry and dead-letter handling, topic fan-out, and metrics - +all registered with a single `AddInMemoryMessaging()` call. + +Queues deliver each message to exactly one subscriber; topics fan every message out to all subscribers. +The contracts live in `SquidStd.Messaging.Abstractions`, so code written against `IMessageQueue` and +`IMessageTopic` moves unchanged to a real broker: use this in-memory provider for single-process apps, +tests, and local dev, then swap in `SquidStd.Messaging.RabbitMq` or `SquidStd.Messaging.Sqs` for +production. ## Install @@ -12,28 +19,108 @@ dotnet add package SquidStd.Messaging ## Usage +Register through the bootstrap - the providers are `ISquidStdService`s, so `StartAsync` / `StopAsync` +manage their lifecycle for you: + ```csharp -using DryIoc; using SquidStd.Messaging.Abstractions.Interfaces; using SquidStd.Messaging.Extensions; +using SquidStd.Services.Core.Extensions; +using SquidStd.Services.Core.Services.Bootstrap; + +var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp"); +bootstrap.ConfigureServices(c => c.RegisterCoreServices().AddInMemoryMessaging()); +await bootstrap.StartAsync(); +``` + +### Queues (competing consumers) + +Each queue message goes to exactly one subscriber. Implement a listener and subscribe it; dispose the +subscription to unsubscribe. + +```csharp +public sealed record OrderPlaced(string Id); + +public sealed class OrderListener : IQueueMessageListenerAsync +{ + public Task HandleAsync(OrderPlaced message, CancellationToken cancellationToken) + { + Console.WriteLine($"queue handled {message.Id}"); + return Task.CompletedTask; + } +} + +var queue = bootstrap.Resolve(); + +using (queue.Subscribe("orders", new OrderListener())) +{ + await queue.PublishAsync("orders", new OrderPlaced("order-1")); +} +``` -var container = new Container(); -container.AddInMemoryMessaging(); +A synchronous `IQueueMessageListener` overload of `Subscribe` is also available. -var queue = container.Resolve(); -await queue.PublishAsync("orders", new { Id = 1 }); +### Topics (fan-out) + +Every subscriber receives every message published to the topic. + +```csharp +var topic = bootstrap.Resolve(); + +using (topic.Subscribe("order-events", (order, _) => +{ + Console.WriteLine($"topic saw {order.Id}"); + return Task.CompletedTask; +})) +{ + await topic.PublishAsync("order-events", new OrderPlaced("order-2")); +} +``` + +## Configuration + +`AddInMemoryMessaging` takes an optional `MessagingOptions`. Options are code-only - this package +registers no YAML config section. + +```csharp +c.AddInMemoryMessaging(new MessagingOptions +{ + MaxDeliveryAttempts = 5, + DeadLetterQueueSuffix = ".dead", + RetryDelay = TimeSpan.FromMilliseconds(250) +}); ``` +| Property | Default | Purpose | +|-------------------------|---------|-------------------------------------------------------------| +| `MaxDeliveryAttempts` | `3` | Delivery attempts before a message is dead-lettered. | +| `DeadLetterQueueSuffix` | `".dlq"`| Suffix appended to a queue name to form its dead-letter queue. | +| `RetryDelay` | `Zero` | Delay before a failed message is re-enqueued. | + +A connection-string overload is also available: +`AddInMemoryMessaging("memory://local?maxDeliveryAttempts=5&retryDelayMs=250&deadLetterSuffix=.dead")`. + +When a listener throws, the message is re-enqueued (after `RetryDelay`) until `MaxDeliveryAttempts` is +reached, then moved to `` - subscribe to that queue to inspect failures. +`MessagingMetricsProvider` is registered as `IMessagingMetrics` / `IMetricProvider` and tracks published, +delivered, retried, failed, and dead-lettered counts per queue. + ## Key types -| Type | Purpose | -|-----------------------------------|-------------------------------------------| -| `MessagingRegistrationExtensions` | `AddInMemoryMessaging(...)` registration. | -| `InMemoryQueueProvider` | Channel-based in-memory `IQueueProvider`. | +| Type | Purpose | +|-----------------------------------|------------------------------------------------------------| +| `MessagingRegistrationExtensions` | `AddInMemoryMessaging(...)` registration. | +| `InMemoryQueueProvider` | Channel-based in-memory `IQueueProvider` with retry/DLQ. | +| `InMemoryTopicProvider` | In-memory `ITopicProvider` (fan-out). | +| `IMessageQueue` / `IMessageTopic` | Typed facades from `SquidStd.Messaging.Abstractions`. | +| `MessagingOptions` | Retry, dead-letter, and delivery-attempt configuration. | ## Related +- Article: [Messaging](https://tgiachi.github.io/squid-std/articles/messaging.html) +- Article: [Messaging abstractions](https://tgiachi.github.io/squid-std/articles/messaging-abstractions.html) - Tutorial: [Messaging](https://tgiachi.github.io/squid-std/tutorials/messaging.html) +- Production transports: [SquidStd.Messaging.RabbitMq](https://tgiachi.github.io/squid-std/articles/messaging-rabbitmq.html), [SquidStd.Messaging.Sqs](https://tgiachi.github.io/squid-std/articles/messaging-sqs.html) ## License diff --git a/src/SquidStd.Persistence.Abstractions/README.md b/src/SquidStd.Persistence.Abstractions/README.md index d41864a..1c05df7 100644 --- a/src/SquidStd.Persistence.Abstractions/README.md +++ b/src/SquidStd.Persistence.Abstractions/README.md @@ -23,6 +23,13 @@ dotnet add package SquidStd.Persistence.Abstractions | `JournalEntityOperationType` | `Upsert` / `Remove` journal operation discriminator. | | `SnapshotSaveStartedEvent` / `SnapshotSaveCompletedEvent` | Snapshot lifecycle events (`IEvent`). | +## Related + +- Article: [Persistence abstractions](https://tgiachi.github.io/squid-std/articles/persistence-abstractions.html) +- Tutorial: [Persistence](https://tgiachi.github.io/squid-std/tutorials/persistence.html) +- [`SquidStd.Persistence`](https://tgiachi.github.io/squid-std/articles/persistence.html) - the binary persistence engine implementing these contracts +- [`SquidStd.Persistence.MessagePack`](https://tgiachi.github.io/squid-std/articles/persistence-messagepack.html) - MessagePack entity descriptors for the engine + ## License MIT - part of [SquidStd](https://github.com/tgiachi/squid-std). diff --git a/src/SquidStd.Persistence.MessagePack/README.md b/src/SquidStd.Persistence.MessagePack/README.md index 3880edf..877b5ed 100644 --- a/src/SquidStd.Persistence.MessagePack/README.md +++ b/src/SquidStd.Persistence.MessagePack/README.md @@ -1,8 +1,12 @@

SquidStd.Persistence.MessagePack

-MessagePack-backed binary `IDataSerializer`/`IDataDeserializer` for `SquidStd.Persistence`. The recommended -compact binary default for entity payloads. Uses the contractless resolver, so plain POCOs need no -attributes. +MessagePack-backed binary `IDataSerializer`/`IDataDeserializer` for SquidStd. `MessagePackDataSerializer` +implements both of SquidStd's serialization contracts over the MessagePack **contractless resolver**, so +plain public POCOs round-trip with no `[MessagePackObject]` attributes and no key annotations. It is the +recommended serializer for `SquidStd.Persistence` journals and snapshots: payloads are a fraction of the +size of JSON and (de)serialization is faster, which matters when every entity write is appended to a +binary journal. `SquidStd.Core` ships a JSON default; pull this package in when you want compact binary +payloads instead. ## Install @@ -12,26 +16,95 @@ dotnet add package SquidStd.Persistence.MessagePack ## Usage +The serializer is a single stateless class - construct it and use it directly. `Serialize` returns a +`ReadOnlyMemory` and `Deserialize` reads one back: + ```csharp using SquidStd.Core.Interfaces.Serialization; using SquidStd.Persistence.MessagePack; -IDataSerializer serializer = new MessagePackDataSerializer(); -IDataDeserializer deserializer = (MessagePackDataSerializer)serializer; +public sealed class Player +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; +} + +var serializer = new MessagePackDataSerializer(); -// Pass to a PersistenceEntityDescriptor, or register in DI: -container.RegisterInstance(new MessagePackDataSerializer()); -container.RegisterInstance(new MessagePackDataSerializer()); +ReadOnlyMemory bytes = serializer.Serialize(new Player { Id = 1, Name = "Bob" }); +Player back = serializer.Deserialize(bytes); ``` -> **Note:** the MessagePack contractless resolver requires **public** entity types. For non-public types, -> use the JSON serializer from `SquidStd.Core` (`JsonDataSerializer`) instead. +### With SquidStd.Persistence + +`PersistenceEntityDescriptor` takes the serializer and deserializer explicitly, so you can use +MessagePack for entity payloads without touching the rest of the app: + +```csharp +using SquidStd.Persistence.Data; +using SquidStd.Persistence.MessagePack; +using SquidStd.Persistence.Services; + +var serializer = new MessagePackDataSerializer(); + +var registry = new PersistenceEntityRegistry(); +registry.Register(new PersistenceEntityDescriptor( + serializer, serializer, typeId: 1, typeName: "Player", schemaVersion: 1, keySelector: p => p.Id)); +``` + +### As the app-wide default serializer + +`RegisterCoreServices()` registers the JSON default via `RegisterDataSerializer()`, which uses +`IfAlreadyRegistered.Keep` - an already-registered serializer wins. So to make MessagePack the app-wide +`IDataSerializer`/`IDataDeserializer` (used by messaging, caching, and DI-registered persistence +descriptors), register it **before** `RegisterCoreServices()`: + +```csharp +using DryIoc; +using SquidStd.Core.Interfaces.Serialization; +using SquidStd.Persistence.MessagePack; +using SquidStd.Services.Core.Extensions; +using SquidStd.Services.Core.Services.Bootstrap; + +var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myapp"); + +bootstrap.ConfigureServices(c => +{ + var serializer = new MessagePackDataSerializer(); + c.RegisterInstance(serializer); + c.RegisterInstance(serializer); + + return c.RegisterCoreServices(); // keeps your MessagePack registration +}); + +await bootstrap.StartAsync(); +``` + +If you register it *after* `RegisterCoreServices()`, the JSON default is already in place and stays - +the order matters, so always register MessagePack first. + +## Notes + +- **Public types only**: the contractless resolver requires **public** entity types. For non-public + types, use the JSON serializer from `SquidStd.Core` (`JsonDataSerializer`) instead. +- **Attribute-free**: `ContractlessStandardResolver` serializes by member name, so no + `[MessagePackObject]`/`[Key]` attributes are needed - but member *names* become part of the payload + contract, so renaming a property is a schema change (bump `schemaVersion` on persisted entities). +- There is no registration extension - the class is the whole package. Register the same instance for + both interfaces so serialize and deserialize agree. ## Key types -| Type | Purpose | -|-----------------------------|-----------------------------------------------------------------| -| `MessagePackDataSerializer` | Contractless MessagePack `IDataSerializer`/`IDataDeserializer`. | +| Type | Purpose | +|-----------------------------|------------------------------------------------------------------| +| `MessagePackDataSerializer` | Contractless MessagePack `IDataSerializer`/`IDataDeserializer`. | + +## Related + +- Article: [Serialization](https://tgiachi.github.io/squid-std/articles/serialization.html) +- Article: [SquidStd.Persistence](https://tgiachi.github.io/squid-std/articles/persistence.html) +- Article: [SquidStd.Persistence.Abstractions](https://tgiachi.github.io/squid-std/articles/persistence-abstractions.html) +- Tutorial: [Durable entity persistence](https://tgiachi.github.io/squid-std/tutorials/persistence.html) ## License diff --git a/src/SquidStd.Telemetry.Abstractions/README.md b/src/SquidStd.Telemetry.Abstractions/README.md index c4f09eb..c4eedbc 100644 --- a/src/SquidStd.Telemetry.Abstractions/README.md +++ b/src/SquidStd.Telemetry.Abstractions/README.md @@ -31,6 +31,12 @@ captures every `SquidStd.*` source automatically. | `OtlpProtocolType` | OTLP transport: gRPC or HTTP. | | `SquidStdActivity` | Shared `ActivitySource` and the `SquidStd.*` source naming convention. | +## Related + +- Article: [Telemetry abstractions](https://tgiachi.github.io/squid-std/articles/telemetry-abstractions.html) +- Guide: [Observability](https://tgiachi.github.io/squid-std/articles/guides/observability.html) +- [`SquidStd.Telemetry.OpenTelemetry`](https://tgiachi.github.io/squid-std/articles/telemetry-opentelemetry.html) - OTLP tracing/metrics provider consuming these options + ## License MIT - part of [SquidStd](https://github.com/tgiachi/squid-std). diff --git a/src/SquidStd.Telemetry.OpenTelemetry/README.md b/src/SquidStd.Telemetry.OpenTelemetry/README.md index 6f04c93..cc2ac3b 100644 --- a/src/SquidStd.Telemetry.OpenTelemetry/README.md +++ b/src/SquidStd.Telemetry.OpenTelemetry/README.md @@ -45,6 +45,12 @@ builder.Services.AddSquidStdTelemetry(new TelemetryOptions { ServiceName = "orde | `TelemetryService` | Configures tracing/metrics providers and exporters from `TelemetryOptions`. | | `MetricsSnapshotBridge` | Exports the existing SquidStd metrics snapshot to OTel instruments. | +## Related + +- Article: [Telemetry OpenTelemetry](https://tgiachi.github.io/squid-std/articles/telemetry-opentelemetry.html) +- Guide: [Observability](https://tgiachi.github.io/squid-std/articles/guides/observability.html) +- [`SquidStd.Telemetry.Abstractions`](https://tgiachi.github.io/squid-std/articles/telemetry-abstractions.html) - `TelemetryOptions` and the `SquidStd.*` ActivitySource convention + ## License MIT - part of [SquidStd](https://github.com/tgiachi/squid-std). diff --git a/src/SquidStd.Templates/README.md b/src/SquidStd.Templates/README.md index d7cecd7..8c7c873 100644 --- a/src/SquidStd.Templates/README.md +++ b/src/SquidStd.Templates/README.md @@ -1,7 +1,9 @@

SquidStd.Templates

-`dotnet new` templates for scaffolding SquidStd projects. The generated projects reference the SquidStd packages -at the same version as this template pack. +`dotnet new` templates for scaffolding SquidStd projects. Each template ships a runnable project - a +`Program.cs` wired to `SquidStdBootstrap`, a `squidstd.yaml` config file, and (for the service templates) +a `Dockerfile`. The generated projects reference the SquidStd packages at the same version as the +template pack, so a scaffolded app always lines up with the libraries it targets. ## Install @@ -9,11 +11,13 @@ at the same version as this template pack. dotnet new install SquidStd.Templates ``` +List the installed templates with `dotnet new list squidstd`. + ## Templates | Template | Short name | What you get | |---------------------|-----------------------|-----------------------------------------------------------------| -| Console host | `squidstd-host` | A `SquidStdBootstrap` console host. | +| Console host | `squidstd-host` | A `SquidStdBootstrap` console host with `RegisterCoreServices`. | | ASP.NET minimal API | `squidstd-aspnetcore` | `UseSquidStd` + health checks + a sample endpoint + Dockerfile. | | Worker microservice | `squidstd-worker` | `AddWorkers` + a sample `IJobHandler` + Dockerfile. | | Worker manager | `squidstd-manager` | `AddWorkerManager` + `MapWorkerManagerEndpoints` + Dockerfile. | @@ -21,16 +25,57 @@ dotnet new install SquidStd.Templates ## Usage ```bash -dotnet new squidstd-worker -n Acme.Resizer -dotnet new squidstd-worker -n Acme.Resizer --messaging inmemory +dotnet new squidstd-host -n Acme.Host +dotnet new squidstd-aspnetcore -n Acme.Api +dotnet new squidstd-worker -n Acme.Worker dotnet new squidstd-manager -n Acme.Manager ``` -`--messaging` (`rabbitmq` default | `inmemory`) is available on the worker and manager templates. +Every generated project builds and runs out of the box: + +```bash +cd Acme.Worker +dotnet run +``` + +The console host, for example, scaffolds the standard bootstrap pattern: + +```csharp +using SquidStd.Services.Core.Extensions; +using SquidStd.Services.Core.Services.Bootstrap; + +var bootstrap = SquidStdBootstrap.Create(options => +{ + options.ConfigName = "squidstd"; +}); + +bootstrap.ConfigureServices(container => container.RegisterCoreServices()); + +await bootstrap.RunAsync(); +``` + +## Template options + +| Option | Templates | Values | Default | +|-----------------|----------------------|---------------------------------|------------| +| `--messaging` | worker, manager | `rabbitmq` \| `inmemory` | `rabbitmq` | +| `--skipRestore` | all | `true` \| `false` | `false` | + +`--messaging` picks the transport the worker and manager templates wire up: `rabbitmq` for real +multi-process deployments, `inmemory` for trying it out in a single process. + +```bash +dotnet new squidstd-worker -n Acme.Resizer --messaging inmemory +dotnet new squidstd-manager -n Acme.Manager --messaging rabbitmq +``` + +`--skipRestore` skips the automatic `dotnet restore` post-action after creation. ## Related - Tutorial: [Scaffolding projects](https://tgiachi.github.io/squid-std/tutorials/scaffolding.html) +- Article: [SquidStd.Workers](https://tgiachi.github.io/squid-std/articles/workers.html) +- Article: [SquidStd.Workers.Manager](https://tgiachi.github.io/squid-std/articles/workers-manager.html) ## License diff --git a/src/SquidStd.Tui/README.md b/src/SquidStd.Tui/README.md index 32d95a0..aceff55 100644 --- a/src/SquidStd.Tui/README.md +++ b/src/SquidStd.Tui/README.md @@ -79,6 +79,11 @@ public sealed class CounterView : TuiComposedView | `TuiApplicationHost` | Boots the Terminal.Gui loop with a root ViewModel. | | `RegisterTui()` / `RegisterView<,>()` | DryIoc registration. | +## Related + +- Article: [Terminal UI](https://tgiachi.github.io/squid-std/articles/tui.html) +- Tutorial: [Terminal UI (MVVM)](https://tgiachi.github.io/squid-std/tutorials/tui.html) + ## License MIT - part of [SquidStd](https://github.com/tgiachi/squid-std). diff --git a/src/SquidStd.Workers/README.md b/src/SquidStd.Workers/README.md index f750bf3..9737583 100644 --- a/src/SquidStd.Workers/README.md +++ b/src/SquidStd.Workers/README.md @@ -1,8 +1,13 @@

SquidStd.Workers

-The SquidStd worker runtime. Consumes `JobRequest`s from the jobs queue, dispatches each to a named -`IJobHandler` (up to `MaxConcurrency` in parallel), and publishes a `WorkerHeartbeat` on the heartbeat topic -every few seconds. +The SquidStd worker runtime. `WorkerConsumerService` consumes `JobRequest`s from the jobs queue and +dispatches each to the `IJobHandler` registered for its job name (up to `MaxConcurrency` in parallel), +while `WorkerHeartbeatService` publishes a `WorkerHeartbeat` on the heartbeat topic every few seconds so +a manager can track the fleet. Use it to build worker microservices that pull jobs off a shared queue; +pair it with `SquidStd.Workers.Manager` on the producing side to enqueue jobs and watch heartbeats. + +The runtime rides on SquidStd messaging, so a transport must be registered alongside it - in-memory for +a single process, RabbitMQ for real multi-process deployments. ## Install @@ -12,18 +17,35 @@ dotnet add package SquidStd.Workers ## Usage +Register a messaging transport, the worker runtime, and your handlers on the bootstrap container: + ```csharp -using DryIoc; using SquidStd.Generators.Workers; -using SquidStd.Workers.Attributes; +using SquidStd.Messaging.Extensions; +using SquidStd.Services.Core.Extensions; +using SquidStd.Services.Core.Services.Bootstrap; using SquidStd.Workers.Extensions; -container.AddInMemoryMessaging(); // or AddRabbitMqMessaging(...) -container.AddWorkers(); -container.RegisterGeneratedJobHandlers(); +var bootstrap = SquidStdBootstrap.Create(o => o.ConfigName = "myworker"); + +bootstrap.ConfigureServices(c => c + .RegisterCoreServices() + .AddInMemoryMessaging() // or AddRabbitMqMessaging(...) + .AddWorkers() + .RegisterGeneratedJobHandlers()); // source-generated [RegisterJobHandler] registrations + +await bootstrap.StartAsync(); ``` +A handler implements `IJobHandler`: it names the job it processes and does the work in `HandleAsync`, +reading string parameters off the `JobRequest`. `[RegisterJobHandler]` opts it into generated +registration (or register manually with `AddJobHandler()`): + ```csharp +using SquidStd.Workers.Abstractions.Data; +using SquidStd.Workers.Attributes; +using SquidStd.Workers.Interfaces; + [RegisterJobHandler] public sealed class ResizeImageHandler : IJobHandler { @@ -31,24 +53,64 @@ public sealed class ResizeImageHandler : IJobHandler public Task HandleAsync(JobRequest job, CancellationToken cancellationToken) { - // job.Parameters["width"], ... + var width = job.Parameters["width"]; + // ... do the work ... return Task.CompletedTask; } } ``` +On the producing side, `SquidStd.Workers.Manager`'s `IJobScheduler` (registered with +`AddWorkerManager()`) publishes the `JobRequest` onto the same queue: + +```csharp +var scheduler = bootstrap.Resolve(); +await scheduler.EnqueueAsync("resize-image", new Dictionary { ["width"] = "640" }); +``` + +An unmatched job name raises `JobHandlerNotFoundException` in the dispatcher. + +## Configuration + +`AddWorkers()` binds the `workers` section of the YAML config to `WorkersConfig`: + +```yaml +workers: + WorkerId: worker-1 + HeartbeatIntervalSeconds: 10 + MaxConcurrency: 4 + JobQueue: squidstd.workers.jobs + HeartbeatTopic: squidstd.workers.heartbeat +``` + +| Property | Default | Notes | +|----------------------------|-------------------------------|------------------------------------------------------------------| +| `WorkerId` | empty | Stable identity; blank falls back to the machine/container name. | +| `HeartbeatIntervalSeconds` | `10` | Seconds between heartbeats; non-positive falls back to 10. | +| `MaxConcurrency` | `0` | Parallel jobs; non-positive falls back to the processor count. | +| `JobQueue` | `squidstd.workers.jobs` | Queue consumed for jobs (`WorkerChannels.JobQueue`). | +| `HeartbeatTopic` | `squidstd.workers.heartbeat` | Topic heartbeats are published to (`WorkerChannels.HeartbeatTopic`). | + +The channel defaults come from `WorkerChannels` in `SquidStd.Workers.Abstractions`, shared with the +manager so both sides agree out of the box. + ## Key types -| Type | Purpose | -|---------------------------------|------------------------------------------------------------------------------| -| `IJobHandler` | Handles jobs of one named kind. | -| `WorkersConfig` | `WorkerId`, `HeartbeatIntervalSeconds`, `MaxConcurrency`, queue/topic names. | -| `RegisterJobHandlerAttribute` | Marks handlers for generated registration. | +| Type | Purpose | +|---------------------------------|-------------------------------------------------------------------------------| +| `IJobHandler` | Handles jobs of one named kind: `JobName` + `HandleAsync(JobRequest, ct)`. | +| `IJobDispatcher` | Routes a `JobRequest` to the handler registered for its job name. | +| `RegisterJobHandlerAttribute` | Marks handlers for source-generated registration. | +| `WorkerConsumerService` | Lifecycle service that consumes the jobs queue and dispatches. | +| `WorkerHeartbeatService` | Lifecycle service that publishes periodic `WorkerHeartbeat`s. | +| `WorkersConfig` | The `workers` config section (identity, heartbeat, concurrency, channels). | | `WorkersRegistrationExtensions` | `AddWorkers()` and `AddJobHandler()`. | ## Related -- Tutorial: [Worker system](https://tgiachi.github.io/squid-std/tutorials/worker-system.html) +- Tutorial: [Build a worker system](https://tgiachi.github.io/squid-std/tutorials/worker-system.html) +- Article: [SquidStd.Workers.Manager](https://tgiachi.github.io/squid-std/articles/workers-manager.html) +- Article: [SquidStd.Workers.Abstractions](https://tgiachi.github.io/squid-std/articles/workers-abstractions.html) ## License