Search code examples
aws-lambdaserverless-frameworkserverlesspact

How to define a Pact contract between a persistent service and a synchronous serverless function?


I have a persistent service which synchronously calls a serverless function (on AWS lambda, via the serverless framework) with some input, the serverless (and stateless) lambda function performs some transformation of the input and synchronously returns the output back to the calling service.

I want to write a Pact contract where the serverless function is the provider and the persistent service is the consumer. How can I do this for a synchronous (i.e. RequestResponse) serverless function?

I have found a few resources on pacts for serverless functions, but they all seem to only tackle the asynchronous use case, unless I'm misunderstanding something. To be clear, the use case in my case is not asynchronous, event-driven message passing, but synchronous calling of a serverless function, blocking while waiting for the response.

From the Pact documentation, I can find references only to support for HTTP-based APIs and Message-based asynchronous APIs. This use case fits neither of these patterns, as we use the serverless framework, which performs the actual HTTP request behind the scenes.

In my case, the persistent service (consumer) is in Java and the serverless function in Kotlin, i.e. both on the JVM.


Solution

  • As mentioned by Matthew Fellows you can configure the AWS Lambda Client to invoke your custom URL since it does regular HTTP request under the hood. Use AWSLambdaClientBuilder and do:

    withEndpointConfiguration(
     AwsClientBuilder.EndpointConfiguration(pactMockServerEndpoint, Regions.EU_CENTRAL_1.name)
    )
    

    You need to set Content-Type: application/json in request headers or else Pact fails to parse header (since it is empty string by default for some reason). Use withRequestHandlers for that and create consumer contracts by calling lambda functions through this client (it should request Pact mock-server instead of AWS).

    However, on the producer side you need to:

    • Create a proxy server (use something like MockServer)
    • Use Pact JUnit provider when running Pact tests so you can start the proxy server
    • Use HTTP target when playing Pact contracts as you normally would do and set proxy as target

    The idea is that you have a proxy HTTP server that uses your serverless handler function for returning responses:

    mockServer.`when`(
        HttpRequest.request().withPath(".*")
    ).respond { httpRequest ->
        HttpResponse.response(
            // Invoke handler and return response
            OBJECT_MAPPER.writeValueAsString(invokeLambdaWithRequest(httpRequest))
        ).let { httpResponse ->
            httpResponse.withHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8")
        }
    }