Running WireMock Server on AWS Lambda
Tags: aws, serverless, cloud, wiremock
Published: 2020-02-27 23:45:09

The serverless paradigm in recent times is getting popular, this was also covered in one of my favorite podcast channel [Syntax.fm](https://syntax.fm) in their recent episode called [Serverless / Cloud Functions - Part 1 — Syntax Podcast 224](https://syntax.fm/show/224/serverless-cloud-functions-part-1).

About Serverless architecture
> Serverless architecture are application designs that incorporate third-party "Backend as a Service" (BaaS) services, and/or that include custom code run in managed, ephemeral containers on a “Functions as a Service” (FaaS) platform. By using these ideas, and related ones like single-page applications, such architectures remove much of the need for a traditional always-on server component. Serverless architectures may benefit from significantly reduced operational cost, complexity, and engineering lead time, at a cost of increased reliance on vendor dependencies and comparatively immature supporting services. -- [reference](https://martinfowler.com/articles/serverless.html)

In this post, we aim to create a mock REST API server using serverless paradigm with AWS lambda and WireMock, because, why not? 

Lambda based REST API mock-server can help us in development for frontend/mobile app, without headaches of maintaining a dedicated server, also AWS provides generous free usage on AWS lambda. 
> The AWS Lambda free usage tier includes 1M free requests per month and 400,000 GB-seconds of compute time per month. [AWS Lambda – Pricing](https://aws.amazon.com/lambda/pricing/)

About [WireMock](http://wiremock.org) as stated on their website:
> WireMock is an HTTP mock server. At its core it is web server that can be primed to serve canned responses to particular requests (stubbing) and that captures incoming requests so that they can be checked later (verification).


## Prerequisites

 - [Java SE Development Kit 8](https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html)
 - [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
 - [Docker](https://docs.docker.com/install/)


## Let's start
Project boilerplate is based on [aws-serverless-java-container/samples/springboot2](https://github.com/awslabs/aws-serverless-java-container/tree/master/samples/springboot2/pet-store)


`build.gradle`
```groovy
dependencies {
  ...
  implementation('org.springframework.boot:spring-boot-starter-web:2.1.8.RELEASE') {
      exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
  }
  
  implementation group: 'com.amazonaws.serverless', name: 'aws-serverless-java-container-springboot2', version: '1.4'
  
  implementation group: 'org.springframework.cloud', name: 'spring-cloud-contract-wiremock', version: '2.2.1.RELEASE'
  ...
}
```

`SpringConfig.java`

```java
@Configuration
public class SpringConfig {

    @Bean
    public WireMockServer getWireMockServer() {
      
       
        WireMockConfiguration options = new WireMockConfiguration()
                 // Mapping files from /src/main/resources are availabe on /var/task in lambda
                .usingFilesUnderDirectory("/var/task/wiremock")  
                 // Start mock server on port 9090. 
                .port(9090)   
                 // Enable Verbose logging
                .notifier(new ConsoleNotifier(true)); 
        return new WireMockServer(options);
    }

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
```

`pet-mapping.json`
Wiremock mapping file, for complex mapping capabilities for request matching of WireMock refer to [WireMock Request Matching](http://wiremock.org/docs/request-matching/)

```json
{
  "request": {
    "url": "/pets",
    "method": "GET"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "body": "[{\"id\":\"7c78a010-e100-48a5-82d8-278e4cab4824\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Daisy\",\"dateOfBirth\":1171968225028},{\"id\":\"0bbb7980-91e5-4e4f-902a-d21f297229f1\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Buddy\",\"dateOfBirth\":1427625825029},{\"id\":\"51f1d89f-8ca0-4fa7-99b7-ce3b6bb45551\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Cooper\",\"dateOfBirth\":1234262625029},{\"id\":\"deece10b-3b6f-4220-b215-93d5e6ce9f04\",\"breed\":\"Afghan Hound\",\"name\":\"Gracie\",\"dateOfBirth\":1335869025029},{\"id\":\"744b70bd-e61a-479e-9f4d-1a07abb46aad\",\"breed\":\"Dalmatian\",\"name\":\"Riley\",\"dateOfBirth\":1490957025029},{\"id\":\"2608727e-892e-4b7a-9cdb-f613a18cddc0\",\"breed\":\"Afghan Hound\",\"name\":\"Riley\",\"dateOfBirth\":1473504225029},{\"id\":\"555d71ab-e1cd-42d2-9264-d323b3c55026\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Sadie\",\"dateOfBirth\":1167907425030},{\"id\":\"2c8dee30-d566-4622-93ae-49ee7073d132\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Max\",\"dateOfBirth\":1294569825030},{\"id\":\"ce6c8a7b-0240-45e9-8188-d38069ca9e0e\",\"breed\":\"Beagle\",\"name\":\"Bella\",\"dateOfBirth\":1373366625030},{\"id\":\"fa20c0f7-4e5c-4e9b-a957-b685329cd4fa\",\"breed\":\"Bloodhound\",\"name\":\"Abby\",\"dateOfBirth\":1149590625030}]"
  }
}
```

`PetsController.java`

```java
@RestController
@EnableWebMvc
public class PetsController {

    private final WireMockServer mockServer;
    private final RestTemplate restTemplate;
    private static final String WIRE_MOCK_BASE_URL = "http://localhost:9090";

    @Autowired
    public PetsController(WireMockServer mockServer, RestTemplate restTemplate) {
        this.mockServer = mockServer;
        this.restTemplate = restTemplate;
    }

    @GetMapping(path = "/mappings")
    public ObjectNode getWireMockMappings() {
        mockServer.start();
        ResponseEntity<ObjectNode> responseEntity =
                restTemplate.getForEntity(WIRE_MOCK_BASE_URL +"/__admin/mappings", ObjectNode.class);
        mockServer.stop();
        return responseEntity.getBody();
    }

    @GetMapping(path = "/pets")
    public Pet[] listPetsWireMock() {
        mockServer.start();
        ResponseEntity<Pet[]> responseEntity =
                restTemplate.getForEntity(WIRE_MOCK_BASE_URL + "/pets", Pet[].class);
        mockServer.stop();
        return responseEntity.getBody();
    }
}
```

## Local Testing 

**Build deployable jar**
```shell
$ ./gradlew clean build
```

**Run Lambda locally with API Gateway**
```shell
$ sam local start-api -t sam.yaml
```


**Get wiremock mappings**
```http
GET http://localhost:3000/mappings
Accept: */*
Cache-Control: no-cache
Postman-Token: 225f9be7-43d1-4d9e-9d84-04775d28e533
Host: localhost:3000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 1537
Server: Werkzeug/1.0.0 Python/3.7.6
Date: Thu, 27 Feb 2020 22:05:21 GMT
```

```json
{
    "mappings": [
        {
            "id": "61d821f0-08a5-4d43-9e5c-2b11624be3dc",
            "request": {
                "url": "/pets",
                "method": "GET"
            },
            "response": {
                "status": 200,
                "body": "[{\"id\":\"7c78a010-e100-48a5-82d8-278e4cab4824\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Daisy\",\"dateOfBirth\":1171968225028},{\"id\":\"0bbb7980-91e5-4e4f-902a-d21f297229f1\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Buddy\",\"dateOfBirth\":1427625825029},{\"id\":\"51f1d89f-8ca0-4fa7-99b7-ce3b6bb45551\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Cooper\",\"dateOfBirth\":1234262625029},{\"id\":\"deece10b-3b6f-4220-b215-93d5e6ce9f04\",\"breed\":\"Afghan Hound\",\"name\":\"Gracie\",\"dateOfBirth\":1335869025029},{\"id\":\"744b70bd-e61a-479e-9f4d-1a07abb46aad\",\"breed\":\"Dalmatian\",\"name\":\"Riley\",\"dateOfBirth\":1490957025029},{\"id\":\"2608727e-892e-4b7a-9cdb-f613a18cddc0\",\"breed\":\"Afghan Hound\",\"name\":\"Riley\",\"dateOfBirth\":1473504225029},{\"id\":\"555d71ab-e1cd-42d2-9264-d323b3c55026\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Sadie\",\"dateOfBirth\":1167907425030},{\"id\":\"2c8dee30-d566-4622-93ae-49ee7073d132\",\"breed\":\"Bernese Mountain Dog\",\"name\":\"Max\",\"dateOfBirth\":1294569825030},{\"id\":\"ce6c8a7b-0240-45e9-8188-d38069ca9e0e\",\"breed\":\"Beagle\",\"name\":\"Bella\",\"dateOfBirth\":1373366625030},{\"id\":\"fa20c0f7-4e5c-4e9b-a957-b685329cd4fa\",\"breed\":\"Bloodhound\",\"name\":\"Abby\",\"dateOfBirth\":1149590625030}]",
                "headers": {
                    "Content-Type": "application/json"
                }
            },
            "uuid": "61d821f0-08a5-4d43-9e5c-2b11624be3dc"
        }
    ],
    "meta": {
        "total": 1
    }
}
```

**Get pets**
```http
GET http://localhost:3000/pets
Accept: */*
Cache-Control: no-cache
Host: localhost:3000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 1149
Server: Werkzeug/1.0.0 Python/3.7.6
Date: Thu, 27 Feb 2020 21:55:27 GMT
```

```json
[
    {
        "id": "7c78a010-e100-48a5-82d8-278e4cab4824",
        "breed": "Bernese Mountain Dog",
        "name": "Daisy",
        "dateOfBirth": 1171968225028
    },
    {
        "id": "0bbb7980-91e5-4e4f-902a-d21f297229f1",
        "breed": "Bernese Mountain Dog",
        "name": "Buddy",
        "dateOfBirth": 1427625825029
    },
    {
        "id": "51f1d89f-8ca0-4fa7-99b7-ce3b6bb45551",
        "breed": "Bernese Mountain Dog",
        "name": "Cooper",
        "dateOfBirth": 1234262625029
    },
    {
        "id": "deece10b-3b6f-4220-b215-93d5e6ce9f04",
        "breed": "Afghan Hound",
        "name": "Gracie",
        "dateOfBirth": 1335869025029
    },
    {
        "id": "744b70bd-e61a-479e-9f4d-1a07abb46aad",
        "breed": "Dalmatian",
        "name": "Riley",
        "dateOfBirth": 1490957025029
    },
    {
        "id": "2608727e-892e-4b7a-9cdb-f613a18cddc0",
        "breed": "Afghan Hound",
        "name": "Riley",
        "dateOfBirth": 1473504225029
    },
    {
        "id": "555d71ab-e1cd-42d2-9264-d323b3c55026",
        "breed": "Bernese Mountain Dog",
        "name": "Sadie",
        "dateOfBirth": 1167907425030
    },
    {
        "id": "2c8dee30-d566-4622-93ae-49ee7073d132",
        "breed": "Bernese Mountain Dog",
        "name": "Max",
        "dateOfBirth": 1294569825030
    },
    {
        "id": "ce6c8a7b-0240-45e9-8188-d38069ca9e0e",
        "breed": "Beagle",
        "name": "Bella",
        "dateOfBirth": 1373366625030
    },
    {
        "id": "fa20c0f7-4e5c-4e9b-a957-b685329cd4fa",
        "breed": "Bloodhound",
        "name": "Abby",
        "dateOfBirth": 1149590625030
    }
]
```

Code for the project can be found here: https://github.com/saurabh73/aws-serverless-wiremock-server

#### Additional Links
- [GitHub - awslabs/aws-serverless-java-container: A Java wrapper to run Spring, Jersey, Spark, and other apps inside AWS Lambda.](https://github.com/awslabs/aws-serverless-java-container)
- [Serverless Architectures](https://martinfowler.com/articles/serverless.html)
- [Spring Boot - WireMock](http://wiremock.org/docs/spring-boot/)
- [Spring Cloud Contract Features](https://cloud.spring.io/spring-cloud-contract/reference/html/project-features.html#features-wiremock)
- [An Intro to Spring Cloud Contract \| Baeldung](https://www.baeldung.com/spring-cloud-contract)