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.
-
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; }} -
Add a response model in
src/Presentation/LambdaFunction/Models/MovieDetail.cs. It reuses the existingReviewmodel.namespace Acme.Reviews.Presentation.LambdaFunction.Models;public record MovieDetail(Guid Id, string Title, List<Review> Reviews); -
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); -
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 -
Add the function to
LambdaFunction.cs.Sendopens 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. -
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(){// Arrangevar movie = (await this.handler.GetMovies(new ListRequest(), this.context)).Items[0];// Actvar result = await this.handler.GetMovie(new GetMovieRequest { Id = movie.Id }, this.context);// Assertresult.Title.ShouldBe(movie.Title);result.Reviews.Count.ShouldBe(movie.ReviewCount);} -
Run it.
Terminal window docker build . --target run --tag acme-reviews:localdocker 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.