Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions .agents/skills/expose-a-use-case/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TResponse> 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`.

Expand All @@ -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 `<param>` tag for each value. Map Application entities to it with a `partial` method on `LambdaFunctionMapper` (`mapping-with-mapperly`). Lists return `Page<T>` through `ToPage`.
4. Add the method to `Handler` with `[LambdaFunction(ResourceName = "<Method>", 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 = "<Method>", 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: `<summary>` with what it returns and in what order, `<param>` for the request and the context, and `<returns>`.
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`).
Expand All @@ -36,21 +36,25 @@ 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<Page<Author>> 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);
});
}
```

## Don't

- 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.
Expand Down
Loading