Chaining Patterns

Chaining is a technique we can use in most of programming languages that consists in calling method after method, thus chaining the methods together,and by doing that we can add some degree of expressivity to our programs. This technique is usually known in the Builder pattern, you can see a patient builder and the example of how you can apply the pattern. Apart from the builder pattern the chaining is almost not used at all in a daily basis when we design our programs and/or APIs. There is, however, another field where we can use chaining. You figure it out, don't you? Yes it is in many of the DSLs we use (and in this case in a daily basis). As an example of DSL we can check this mocking framework, called Mocking Server. If you get into the work of checking the page you'll notice that the very first example of how to use the API looks something like this (maybe in the future this will be updated so lets assume it just looks like)

new MockServerClient("localhost", 1080)
        .when(
                request()
                        .withMethod("POST")
                        .withPath("/login")
                        .withBody("{username: 'foo', password: 'bar'}")
        )
        .respond(
                response()
                        .withStatusCode(302)
                        .withCookie(
                                "sessionId", "2By8LOhBmaW5nZXJwcmludCIlMDAzMW"
                        )
                        .withHeader(
                                "Location", "https://www.mock-server.com"
                        )
        );

Just by looking at the code we immediately see the expressiveness of the language and how it can help us write code that is easy to read and maintain. But the really question is, how do we design an API like this? Good question, you really are a clever folk! Well the answer is not trivial, and many tricks are used internally but one of the most important, that seasoned developers have in their pocket, is the chaining technique. So lets try to implement ourselfs a DSL just like this. Well not exactly like this because we do not want to deal with all the fuss of implementing a http server, but we can just ignore that part and go happy all along if our purpose is to design the API level of this framework. By analysing the DSL we noted at least three structures that are implemented with the chaining technique. Looking outside in, you notice, that MockServerClient has .when and .respond methods that are chained together. Inside you got two builder methods called request() and response() that are used to create request and response objects respectively. But, you may ask, where is the build method invocation, that is so common on build classes, that follow the builder pattern? Good question! Well it is not there because the builder is passed into the when/responde method(s) and not the builded object. What this tell us is that somewhere inside the code the build() method will be called to create requests and responses objects. So our SillyMockServer will look like this

package com.balhau.tuts.exercises.chaining.sillymockserver;

import java.util.ArrayList;
import java.util.List;

public class SillyMockServerClient {
    private final int port;
    private final String host;
    private final List<SillyRequest> requests;
    private final List<SillyResponse> responses;

    public SillyMockServerClient(String host,int port){
        this.port=port;
        this.host=host;
        this.requests=new ArrayList<>();
        this.responses=new ArrayList<>();
    }

    public SillyMockServerClient when(SillyRequestBuilder request){
        this.requests.add(request.build());
        return this;
    }

    public SillyMockServerClient respond(SillyResponseBuilder response){
        this.responses.add(response.build());
        return this;
    }

    private int findRequest(SillyRequest request){
        int aux=0;
        for(SillyRequest current : requests){
            if(SillyRequest.isEquivalent(current,request)) {
                return aux;
            }
            aux++;
        }
        return -1;
    }

    public static SillyResponseBuilder response(){
        return new SillyResponseBuilder();
    }

    public static SillyRequestBuilder request(){
        return new SillyRequestBuilder();
    }



    public SillyResponse call(SillyRequest request) throws Exception{
        int pos = findRequest(request);
        if(pos==-1){
            throw new Exception("Request not mapped");
        }
        return this.responses.get(findRequest(request));
    }
}

Now that we got the SillyMockServer implemented we need to tackle the four missing components. The SillyRequest, SillyResponse and the respective builders. Well, no rocket science here and the code follows

public class SillyRequest {
    private final String method;
    private final String path;
    private final Map<String,String> headers;
    private final String body;

    public SillyRequest(String method,String path,Map<String,String> headers,String body){
        this.method=method;
        this.path=path;
        this.headers=headers;
        this.body=body;
    }

    public static boolean isEquivalent(SillyRequest requestA,SillyRequest requestB){
        if (requestA.equals(requestB)){
            return true;
        }
        if(
                requestA.method.equals(requestB.method) &&
                        requestA.path.equals(requestB.path) && requestA.body.equals(requestB.body) && containsHeaders(requestA.headers,requestB.headers)
                ){
            return true;
        }
        return false;

    }

    private static boolean containsHeaders(Map<String,String> superset,Map<String,String> subset){
        Set<String> keys = subset.keySet();
        for(String key : keys){
            if(!superset.containsKey(key) && !superset.get(key).equals(subset.get(key))){
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        return new Gson().toJson(this);
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
}

The SillyRequest has a little bit more of logic because we need to match requests into responses and here is where I chose to place that logic (that matches equivalent requests). The SillyResponse is pretty straightforward and serves only as a container for the responses

public class SillyResponse {
    public final int httpStatus;
    public final Map<String,String> cookies;
    public final Map<String,String> headers;
    public final String body;

    public SillyResponse(int httpStatus, Map<String, String> cookies, Map<String, String> headers, String body) {
        this.httpStatus = httpStatus;
        this.cookies = cookies;
        this.headers = headers;
        this.body = body;
    }

    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}

Next we need to implement the builders and here nothing new

public class SillyRequestBuilder implements Builder<SillyRequest>{
    private String method;
    private String path;
    private Map<String,String> headers;
    private String body;

    public SillyRequestBuilder(){
        this.headers=new HashMap<>();
    }

    public SillyRequestBuilder withMethod(String method){
        this.method=method;
        return this;
    }

    public SillyRequestBuilder withPath(String path){
        this.path=path;
        return this;
    }

    public SillyRequestBuilder withHeader(String key,String val){
        this.headers.put(key,val);
        return this;
    }

    public SillyRequestBuilder withBody(String body){
        this.body=body;
        return this;
    }

    @Override
    public SillyRequest build() {
        return new SillyRequest(
                method,path,headers,body
        );
    }
}

and the SillyResponseBuilder follows

public class SillyResponseBuilder implements Builder<SillyResponse>{

    public int httpStatus;
    public Map<String,String> cookies;
    public Map<String,String> headers;
    public String body;

    public SillyResponseBuilder(){
        this.headers=new HashMap<>();
        this.cookies=new HashMap<>();
    }

    public SillyResponseBuilder withCookie(String key,String value){
        this.cookies.put(key,value);
        return this;
    }

    public SillyResponseBuilder withHeader(String key,String value){
        this.headers.put(key,value);
        return this;
    }

    public SillyResponseBuilder withBody(String body){
        this.body=body;
        return this;
    }

    public SillyResponseBuilder withHttpStatus(int httpStatus){
        this.httpStatus=httpStatus;
        return this;
    }

    @Override
    public SillyResponse build() {
        return new SillyResponse(httpStatus,cookies,headers,body);
    }
}

Now we are in position to test all of this SillyStuff with a simple proof of concept, so without further addo

public class SillyMockServerExample {
    public static void main(String[] args) throws  Exception{
        SillyMockServerClient mockServerClient =
                new SillyMockServerClient("localhost",10000)
                .when(
                        request()
                            .withMethod("POST")
                            .withPath("/hello")
                            .withBody("<hello>sir</hello>")
                )
                .respond(
                        response()
                            .withHttpStatus(302)
                            .withBody("OK")
                            .withCookie("sessionId","ASDASDALSDHASHDKJA")
                            .withHeader("Location","http://serverendpoint.local")
                );

        System.out.println(mockServerClient.call(request()
                .withMethod("POST")
                .withPath("/hello")
                .withBody("<hello>sir</hello>").build()));

    }
}

If you run it you'll get as a result

    {
        "httpStatus": 302,
        "cookies": {
            "sessionId": "ASDASDALSDHASHDKJA"
          },
          "headers": {
            "Location": "http://serverendpoint.local"
          },
          "body": "OK"
    }

as expected. This is just a simple exercise that should help you understand a little bit better the ideas behind chaining and what we can get with it in practice. You can clone the code here and test and tweek as you want. Cheers