From ca4213f8a22a750af40b0d1c56c776c7d54e8988 Mon Sep 17 00:00:00 2001 From: Stephen Walsh <868017+stphnwlsh@users.noreply.github.com> Date: Sun, 8 Nov 2026 16:03:21 +0000 Subject: [PATCH] Wrap every function in Invoke in the expose-a-use-case skill The skill told agents to call Send on its own, so a function written from it left no trace span and its metrics waited for the next invocation to be sent. The example and the steps now wrap the body in Invoke, as every function in the template does, and the Don't list says why. --- .agents/skills/expose-a-use-case/SKILL.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.agents/skills/expose-a-use-case/SKILL.md b/.agents/skills/expose-a-use-case/SKILL.md index 77d7527..193379e 100644 --- a/.agents/skills/expose-a-use-case/SKILL.md +++ b/.agents/skills/expose-a-use-case/SKILL.md @@ -11,7 +11,7 @@ metadata: Every function is a public method on `Handler` (`src/Presentation/LambdaFunction/LambdaFunction.cs`) with a `[LambdaFunction]` attribute and the signature `Task Name(TRequest request, ILambdaContext context)`. [Lambda Annotations](https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.Annotations) generates a class for each one, such as `Handler_GetMovies_Generated`, and that class is what Lambda calls. It builds the services from `Startup` once per execution environment, creates the `Handler` as a singleton, and writes the function into `src/Presentation/serverless.template` when the project builds. -The `Handler` constructor creates or upgrades the database once. Each method maps its request to the Application request, sends it through the private `Send` helper and maps the result to a presentation model. `Send` opens a DI scope per invocation and cancels the work when the invocation's remaining time runs out. +The `Handler` constructor creates or upgrades the database once. Each method wraps its body in the private `Invoke` helper, which opens the function's trace span and flushes the metrics when the invocation ends, and inside it maps its request to the Application request, sends it through the private `Send` helper and maps the result to a presentation model. `Send` opens a DI scope per invocation and cancels the work when the invocation's remaining time runs out. A list that callers also want over HTTP gets a second method in the `#region HTTP API` block, named with an `Http` prefix (`HttpGetMovies`). It has `[HttpApi(LambdaHttpMethod.Get, "/movies")]` beside `[LambdaFunction]`, takes its inputs as `[FromQuery]` parameters and returns `IHttpResult`. It builds the same request record and calls the direct method through the `Http` helper, which answers `200` with the result and `400` problem details for a `ValidationException`. The build adds the route to the stack's API Gateway HTTP API in `serverless.template`. @@ -22,7 +22,7 @@ Functions contain no logic. Validation, not-found and everything else happen in 1. Add the use case first (`add-a-use-case`). 2. Add the request as a `record` in `src/Presentation/LambdaFunction/Models/` with `init` properties, defaults where the Application request has them, and XML docs on the type and each property. 3. Add or reuse a response `record` in the same folder, with a `` tag for each value. Map Application entities to it with a `partial` method on `LambdaFunctionMapper` (`mapping-with-mapperly`). Lists return `Page` through `ToPage`. -4. Add the method to `Handler` with `[LambdaFunction(ResourceName = "", PackageType = LambdaPackageType.Image, MemorySize = 256, Timeout = 30)]`, and send through `this.Send(..., context)`. Never resolve services or create tokens yourself. +4. Add the method to `Handler` with `[LambdaFunction(ResourceName = "", PackageType = LambdaPackageType.Image, MemorySize = 256, Timeout = 30)]`. Wrap the body in `this.Invoke(request, context, ...)`, so the invocation is traced and its metrics are flushed, and send through `this.Send(..., context)`. Never resolve services or create tokens yourself. 5. Add every new request and response type to `LambdaFunctionJsonSerializerContext` with `[JsonSerializable(typeof(...))]` (`json-serialization`). 6. Document the method: `` with what it returns and in what order, `` for the request and the context, and ``. 7. Test it through the handler in `tests/Presentation.Tests.Integration/HandlerTests.cs` (`presentation-integration-tests`). Cover each success and each validation or not-found failure (`test-design`). @@ -36,14 +36,17 @@ Functions contain no logic. Validation, not-found and everything else happen in [LambdaFunction(ResourceName = "GetAuthors", PackageType = LambdaPackageType.Image, MemorySize = 256, Timeout = 30)] public async Task> GetAuthors(ListRequest request, ILambdaContext context) { - var page = await this.Send(new GetAuthorsQuery + return await this.Invoke(request, context, async () => { - Query = request.Query, - Offset = request.Offset, - Limit = request.Limit - }, context); + var page = await this.Send(new GetAuthorsQuery + { + Query = request.Query, + Offset = request.Offset, + Limit = request.Limit + }, context); - return ToPage(page, this.mapper.ToPresentation); + return ToPage(page, this.mapper.ToPresentation); + }); } ``` @@ -51,6 +54,7 @@ public async Task> GetAuthors(ListRequest request, ILambdaContext c - Return Application entities. Map them to the records in `Models/`, so the function's contract doesn't move when Application does. - Catch exceptions or check `null` in a function. +- Skip `Invoke`. A function that calls `Send` on its own still works, but it leaves no trace and its metrics wait for the next invocation to be sent. - Build a new `ServiceProvider` per invocation, or register services anywhere but `Startup`. The generated class builds them once, and `Send` scopes each call. - Take services as `[FromServices]` parameters. Lambda Annotations 2.4 writes a documentation link for them that doesn't resolve, and the build fails on the warning. - Edit a function's entry in `serverless.template` by hand. The build rewrites it from the attribute. Add other resources and settings beside it.