DEV Community

Saurabh Dutta
Saurabh Dutta

Posted on

Running WireMock Server on AWS Lambda

The serverless paradigm in recent times is getting popular, this was also covered in one of my favorite podcast channel Syntax.fm in their recent episode called Serverless / Cloud Functions - Part 1 — Syntax Podcast 224.

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

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

About WireMock 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

Let's start

Project boilerplate is based on aws-serverless-java-container/samples/springboot2

build.gradle

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

@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

{
  "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

@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

$ ./gradlew clean build

Run Lambda locally with API Gateway

$ sam local start-api -t sam.yaml

Get wiremock mappings

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
{
    "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

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
[
    {
        "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

Top comments (0)