The lightweightness of microservices – Comparing Spring Boot, WildFly Swarm, and Haskell Snap

A microservice is an autonomous sub application for a strictly defined and preferably small domain. An application built from microservices is scalable, resilient, and flexible. At least, if the services and their infrastructure are well designed. One requirement on the used frameworks to achieve scalability and resilience is that they are lightweight. Lightweightness comes in different flavors. Microservices should be stopped and started fastly, and should consume few resources. The development and maintenance of microservices should be easy.

For this reason, in the Java world, Spring Boot is currently recommended as best choice regarding these requirements. Traditional Java EE application servers are too heavyweight, because they are not developed as basis for single services but as platform for running different applications simultaneously. Thus, they must be bloated.

Being a curious person I used some of my spare time in the last Christmas holidays to actually measure the lightweightness. First I chose Spring Boot and WildFly as “competitors”. I added WildFly Swarm which provides similar features as Spring Boot but is based on WildFly. Then looking at the requirements I decided to include a framework with a real small startup time in comparison to Java-based frameworks and chose Snap based an Haskell. For every framework I built a minimal micro service, wrapped it into a Docker container, and measured its weight.

Weight of the implementation effort

Each microservice takes two floats as input and sums them up. The service is available as GET-Request, the parameters are URL query parameter. You can find all implementations on GitHub (GitHub-Repo).

The implementation in Spring is straight forward:

@Path("/adder")
public class AdderService {
    @GET
    public float add(@QueryParam("summand1") float summand1,
                     @QueryParam("summand2") float summand2) {
        return summand1 + summand2;
    }
}

I omit the implementations for the WildFly variants, because they are more or less the same as the Spring Boot implementation. The Haskell variant is more complex:

adderHandler = do
    parameters <- getsRequest rqParams
    let result = do
            summand1StringList <- Map.lookup "summand1" parameters
            summand1 <- convertUniqueParameterToFloat summand1StringList
            summand2StringList <- Map.lookup "summand2" parameters
            summand2 <- convertUniqueParameterToFloat summand2StringList
            return (summand1 + summand2)
    case result of
        Nothing -> writeBS "Error - incorrect paramaters"
        Just f -> writeLBS $ toLazyByteString (floatDec f)

There are two differences to the Java solution. First, the conversion of the request parameter to a float value must be done manually. (I left out the conversion code itself.) Second, the Haskell framework forces the developer to check for any conversion error and handle them manually.

On the Java side, the framework handles the conversion and the error handling. If a non-float parameter is given, WildFly raises a generic not-found 404 return code, whereas Spring Boot returns a bad-request 400 with some details.

Looking at the frameworks, the development in Java provides the most comfort. But the Snap framework in Haskell also offers everything needed for quick development.

To startup the Spring Boot framework a dedicated Java class is needed:

public class StartupSpring {
    public static void main(String[] args) {
        SpringApplication.run(AdderService.class, args);
    }
}

The plain Java EE solution implemented for WildFly needs two things: First, a configuration file jboss-web.xml to set the URL context of the application:

<jboss-web>
    <context-root>/</context-root>
</jboss-web>

Second, a configuration class for the REST-framework:

@ApplicationPath("/")
public class RestApplication extends Application {}

WildFly Swarm uses the same configuration class for the REST framework, but does not need the configuration of the URL context. For faster and more configurable startup of the runtime, WildFly Swarm supports a startup class similar to Spring Boot.

public class StartServer {
    public static void main(String[] args) throws Exception {
        new Swarm()
                .start()
                .deploy();
    }
}

Snap offers a short convenience function to easily set up a simple web service.

main :: IO ()
main = quickHttpServe $
            route [  ("adder", adderHandler) ]

For the given microservice, the Snap framework provides the most simple startup. Spring Boot provides a single point of configuration. The configuration of WildFly is easy, but divided into two different files. WildFly Swarm supports a dedicated startup class but also needs the distributed Java EE configuration files.

The microservices implemented in Java are built with Maven. The Docker plugin from Spotify is used to create the docker images. The Haskell Snap service is built using Stack. All build systems support docker and are pretty easy to use. Because Haskell creates binaries, the build must be done inside a Docker container whereas the Java based services can be built directly on the developer system.
Docker is supported well by all platforms.

Weight of Bits and Bytes

There are (at least) two interesting things to measure: the size of the generated service and the size of the complete docker image. This diagram shows theses two metrics:

Sizes of the generated artifacts

The blue bars depict the size of the services. The smallest one is the pure Java EE implementation with only 4kB. This is no surprise because it just consists of the application code without any infrastructure, such as a web server. Next in size is the Snap service with 8 MB containing everything needed for running. Spring Boot with 14 MB also contains everything but the Java basis SDK which weighs additional several MBs. The biggest executable is the WildFly Swarm Service with 45 MB, more than three times the size of the Spring Boot service.

The green bars show the overall size of the docker image. Both Spring Boot and WildFly Swarm work with the optimized Docker open-jdk image based on Alpine. Hence, they have the smallest footprints, 159MB are used by Spring Boot and 190MB by WildFly Swarm. The official WildFly image is based on the much heavier Centos Docker image and consumes in sum 583MB. The Snap service is based on Ubuntu and lies with 234MB in between these two groups.

The question now is what to make with these numbers. Docker supports a file system based on reusable layers. If there are many WildFly services, they all share the basic layers which provide the operating system, the Java stack, and the application servier. Thus, in this use-case only the size of the generated executable is of importance. However, if all microservices are very different from each other, in the sense that they use different implementation platforms, there are not many layers to share. Thus, the overall size of the container matters more.

So, it depends. If you have a technologically homogeneous services landscape, the good old Java EE application server approach offers a smaller resource consumption than the newer approaches. WildFly Swarm in comparison with Spring Boot is surprisingly heavy.

Weight of Time

IMHO there are two interesting characteristic numbers regarding time: First the build time of a service, second the startup time. This diagram shows the result of my measurements:

times.png

The blue bars depict the build time. The pure Java EE micro service with 7s is faster than the two Java alternatives which additionally have to bundle the application code with the execution environment. Surprisingly WildFly Swarm needs nearly twice the time to build than Spring Boot (16s to 9s). Because the Haskell based Snap Service must be compiled inside a Docker container it needs 13s to build.

The green bars show  the startup time of the services as Docker containers including the boot time of the underlying operating system. Spring Boot and WildFly have more or less the same startup time, which was expected. A little bit surprising is that WildFly Swarm needs 17% more startup time than WildFly. The Snap service is with a factor of 8 the by far fastest service.

My Insights…

From the results of these experiments, IMHO some conclusions can be drawn.

  • WildFly Swarm has still some way to go. It is significantly slower than Spring Boot and uses more resources.
  • Java EE with WildFly as application server is a viable platform for Java-based microservices.
  • If requirements like minimal startup time and resource consumptions are paramount, then it makes sense to look for alternatives to Java.

Of course, I am aware, that there exist much more parameters which are also important for choosing a suitable implementation platform. Nevertheless, I hope these results can help you a little bit making the right decision for your project.


Comments

8 responses to “The lightweightness of microservices – Comparing Spring Boot, WildFly Swarm, and Haskell Snap”

  1. This would be interesting. Maybe next Xmas break. 🙂
    If you like, you can do the implementation and I do the benchmarking.

  2. Phuc Tran

    add Microprofile.io and benchmarks of it against Spring Boot. I will love to check back.

  3. As for lightweight Java solutions – take a look on Dropwizard (http://www.dropwizard.io) and Undertow (http://undertow.io/)

  4. Bernd

    Nice article. Thanks! I’d be interested to see how http://vertx.io/ compares.

  5. Thank you for the hint!

    I will add Spark when I find some spare time.

  6. For smaller Java frameworks take a look at Spark ( http://sparkjava.com/ ) which uses Jetty and starts in under 1s on my developer laptop.

  7. I got a hint from a colleague of mine regarding the error handling of the Java frameworks and edited the corresponding paragraph.

Discover more from akquinet - Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading

WordPress Cookie Notice by Real Cookie Banner