Skip to content
Clean Lambda Function
Lean Mean Clean Apps

Five templates, one search box. Everything in the docs is in here, including the bits we hoped you'd skim.

Try one of these

Results say which template they came from, so you won't end up reading about Lambda when you wanted GraphQL. Again.

Add a function

This guide adds a GetMovie function that returns one movie with all of its reviews. The Application layer already has a GetMovieByIdQuery, so all the work happens in Presentation. That’s the point of the layers: new ways in, same rules underneath.

  1. Add a request model in src/Presentation/LambdaFunction/Models/GetMovieRequest.cs. This is the JSON your callers send.

    namespace Acme.Reviews.Presentation.LambdaFunction.Models;
    /// <summary>
    /// Gets one movie and its reviews.
    /// </summary>
    public record GetMovieRequest
    {
    /// <summary>The id of the movie to get.</summary>
    public Guid Id { get; init; }
    }
  2. Add a response model in src/Presentation/LambdaFunction/Models/MovieDetail.cs. It reuses the existing Review model.

    namespace Acme.Reviews.Presentation.LambdaFunction.Models;
    public record MovieDetail(Guid Id, string Title, List<Review> Reviews);
  3. Teach the mapper about it in Mapping/LambdaFunctionMapper.cs. Mapperly writes the code at build time, including the list of reviews.

    using AppMovieDetail = Application.Movies.Entities.Movie;
    // ...
    using PresentationMovieDetail = Models.MovieDetail;
    public partial PresentationMovie ToPresentation(AppMovie movie);
    public partial PresentationMovieDetail ToPresentation(AppMovieDetail movie);
  4. Register both types with the source-generated serializer in Serialization/LambdaFunctionJsonSerializerContext.cs. Skip this and Lambda can’t read the request.

    [JsonSerializable(typeof(Page<Movie>))]
    [JsonSerializable(typeof(Page<Author>))]
    [JsonSerializable(typeof(Page<Review>))]
    [JsonSerializable(typeof(GetMovieRequest))]
    [JsonSerializable(typeof(MovieDetail))]
    public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext
  5. Add the function to LambdaFunction.cs. Send opens a scope, runs the query through validation and stops it if the invocation runs out of time.

    /// <summary>
    /// Gets one movie and every review of it.
    /// </summary>
    /// <param name="request">The id of the movie.</param>
    /// <param name="context">The Lambda invocation context.</param>
    /// <returns>The movie and its reviews.</returns>
    public async Task<MovieDetail> GetMovie(GetMovieRequest request, ILambdaContext context)
    {
    var movie = await this.Send(new GetMovieByIdQuery { Id = request.Id }, context);
    return this.mapper.ToPresentation(movie);
    }

    Add using Application.Movies.Queries.GetMovieById; at the top.

  6. Test it in tests/Presentation.Tests.Integration/HandlerTests.cs. The class already has a handler with its own seeded database.

    [Fact]
    public async Task GetMovie_ShouldReturn_TheMovieAndItsReviews()
    {
    // Arrange
    var movie = (await this.handler.GetMovies(new ListRequest(), this.context)).Items[0];
    // Act
    var result = await this.handler.GetMovie(new GetMovieRequest { Id = movie.Id }, this.context);
    // Assert
    result.Title.ShouldBe(movie.Title);
    result.Reviews.Count.ShouldBe(movie.ReviewCount);
    }
  7. Run it.

    Terminal window
    docker build . --target run --tag acme-reviews:local
    docker run --rm -p 9000:8080 -e Database__SeedSampleData=true acme-reviews:local \
    "Presentation::Acme.Reviews.Presentation.LambdaFunction.Handler::GetMovie"
    curl -X POST "http://localhost:9000/2015-03-31/functions/function/invocations" \
    -d '{"id":"e159975e-4e6a-1ffd-713e-1eb9d8d4a593"}'

An unknown id fails with a NotFoundException, and an empty one fails validation, both before your code has to worry about it.