BT

Build High Performance JVM Microservices with Ratpack & Spring Boot

Posted by Dan Woods on Jul 28, 2015 |

 

Ratpack and Spring Boot are a match made in microservice heaven. Each is a developer-centric web framework for the JVM, focused on productivity, efficiency, and lightweight deployments. They have their respective benefits in the area of microservice development, in that they bring different offerings to the table. Ratpack brings a reactive programming model with a high throughput, non-blocking web layer, and a convenient handler chain for defining application structure and HTTP request processing; Spring Boot brings an integration to the entire Spring ecosystem, and simplistic way to configure and autowire components into an application. For building cloud-native and data-driven microservices, they are a compliment that is unparalleled.

Ratpack ships with no opinion on an application’s underlying dependency injection framework. Instead, it allows applications to access service layer components through its DI abstraction, known as the Registry. Ratpack’s Registry is an integral aspect of its infrastructure, providing an interface for DI providers to participate in the component resolution sequence through a registry backing.

Out of the box, Ratpack ships with registry backings for both Guice and Spring Boot, giving the flexibility of implementation back to the developer.

In this post, we will demonstrate building a RESTful data-driven Ratpack and Spring Boot microservice that leverages Spring Data behind the scenes.

The best way to get started with a Ratpack project is by creating a Gradle build script and the standard Java project structure. Gradle is the supported build system for Ratpack, but since Ratpack is simply a collection of JVM libraries, it can really be built by any build system (although your mileage may vary). The easiest way to get Gradle installed if you don’t already have it is through the Groovy enVironment Manager. Our project’s buildscript is depicted in Listing 1.

Listing 1

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'io.ratpack:ratpack-gradle:0.9.18'
  }
}

apply plugin: 'io.ratpack.ratpack-java'
apply plugin: 'idea'
apply plugin: 'eclipse'

repositories {
  jcenter()
}

dependencies {
  compile ratpack.dependency('spring-boot') (1)
}

mainClassName = "springpack.Main" (2)

eclipse {
  classpath {
    containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
    containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
  }
}

The buildscript imports the Ratpack Spring Boot integration through the use of the Ratpack Gradle plugin’s ratpack.dependency(..) capability at (1). With the buildscript and project structure in place, we can create a "main class", which will be the runnable class to start and run our application. Note that at (2) we are specifying the main class name so that the command line tooling will work better. This means that our main class must correspond to this, so we’ll create a springpack.Main class in the src/main/java tree of the project.

Within the main class, we build an instance of a RatpackServer through a factory method, start, to which we supply the definition of our application. Within this definition will exist our RESTful HTTP API handler chain. As an initial demonstration, consider the main class shown in Listing 2. Note that Ratpack requires Java 8.

Listing 2

package springpack;

import ratpack.server.RatpackServer;

public class Main {

  public static void main(String[] args) throws Exception {
    RatpackServer.start(spec -> spec
      .handlers(chain -> chain (1)
          .prefix("api", pchain -> pchain (2)
            .all(ctx -> ctx (3)
              .byMethod(method -> method (4)
                .get(() -> ctx.render("Received GET request"))
                .post(() -> ctx.render("Received POST request"))
                .put(() -> ctx.render("Received PUT request"))
                .delete(() -> ctx.render("Received DELETE request"))
              )
            )
          )
      )
    );
  }
}

If we dissect the application definition within the main class, we can identify a few key areas that are worth explaining for those unfamiliar with Ratpack. The first notable point is that HTTP requests in Ratpack flow through a handler chain as defined by the handlers section of the definition at (1). Handlers are defined within the chain that describe the type of request they are capable of satisfying. Specifically, at (2) we define a prefix handler type, and specify that it should bind to the "api" HTTP route. The prefix handler, in turn created a new chain that will be delegated to for incoming requests that match the "/api" endpoint. At (3) we use the all handler type to specify that all incoming requests should be run through the provided handler, and at (4) we use Ratpack’s byMethod mechanism to bind get, post, put, and delete handlers to their respective HTTP methods.

We can now run the application from the command line by simply issuing the gradle “run” command at the root of the project. This will start and bind the webserver on port 5050. To demonstrate the existing functionality of the project and to make sure the handler structure is working as expected, we can run a few test with curl from the command line:

As you can see, the application handler chain is properly routing the request, and we have the structure for our RESTful API in place. Now we need to make it do something…​

For the sake of demonstration, let’s keep it simple and make this microservice responsible for the CRUD operations related to a User domain object. Through the REST endpoint, clients should be able to:

  • request a specific user account through a GET request with the username as a path variable;
  • list all users through a GET request when no username is specified;
  • create a user by POSTing a JSON encoded user object;
  • update the email address of a user by issuing a PUT request with the username as a path variable;
  • delete a user by issuing a DELETE request with the the username as a path variable.

Most of the infrastructure for handling these requirements is already in place based on the handlers we defined in the prior section, but the requirements mean that we will need to change things slightly. For example, we will now need to bind handlers that accept the username path variable. The updated code in Listing 3 shows the main class, now with handlers to match the requirements.

Listing 3

package springpack;

import ratpack.server.RatpackServer;

public class Main {

  public static void main(String[] args) throws Exception {
    RatpackServer.start(spec -> spec
      .handlers(chain -> chain
        .prefix("api/users", pchain -> pchain (1)
          .prefix(":username", uchain -> uchain (2)
            .all(ctx -> { (3)
              String username = ctx.getPathTokens().get("username");
              ctx.byMethod(method -> method (4)
                .get(() -> ctx.render("Received request for user: " + username))
                                               .put(() -> {
                  String json = ctx.getRequest().getBody().getText();
                  ctx.render("Received update request for user: " + username + ", JSON: " + json);
                })
                .delete(() -> ctx.render("Received delete request for user: " + username))
              );
            })
          )
          .all(ctx -> ctx (5)
            .byMethod(method -> method
              .post(() -> { (6)
                String json = ctx.getRequest().getBody().getText();
                ctx.render("Received request to create a new user with JSON: " + json);
              })
              .get(() -> ctx.render("Received request to list all users")) (7)
            )
          )
        )
      )
    );
  }
}

The API has now been restructured to follow a more resource-oriented pattern centralized around our user domain object with the following changes:

  • at (1), we change the entry-level prefix to /api/users;
  • at (2), we bind a new prefix handler, this time on the :username path variable. Any value present in the incoming request path will be translated and made accessible to the Ratpack handler via the ctx.getPathTokens() map;
  • at (3), we bind a handler for all traffic following the /api/users/:username URI pattern;
  • and at (4) we use the byMethod mechanism to attach handlers to the HTTP GET, PUT, and DELETE methods. These handlers allow us to understand the intention of the client’s operation against a given user. Within the PUT handler, we make the ctx.getRequest().getBody().getText() call to capture the JSON from the incoming request;
  • at (5), we attach a handler to match all incoming requests to the /api/users endpoint;
  • at (6), we leverage the byMethod mechanism again within the /api/users handler to attach a POST handler that called when creating new users. We again make a call to capture the JSON from the incoming request;
  • and finally, at (7), we attach the GET handler that will be called when a client desires a list of all users.

If you again start the application and make another series of curl command line calls, we can test that the endpoints are operating as intended:

Now that we have the scaffolding in place that represents the requirements for our API, we need to make it do something useful. We can start by setting up the dependencies for our service layer. In this example, we will leverage the Spring Data JPA project for our data access object; the changes to the buildscript are reflected in Listing 4.

Listing 4

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath 'io.ratpack:ratpack-gradle:0.9.18'
  }
}

apply plugin: 'io.ratpack.ratpack-java'
apply plugin: 'idea'
apply plugin: 'eclipse'

repositories {
  jcenter()
}

dependencies {
  compile ratpack.dependency('spring-boot')
  compile 'org.springframework.boot:spring-boot-starter-data-jpa:1.2.4.RELEASE' (1)
  compile 'com.h2database:h2:1.4.187' (2)
}

mainClassName = "springpack.Main"

eclipse {
  classpath {
    containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
    containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
  }
}

 

The only changes are at (1), we now include the Spring Boot Spring Data JPA dependency, and at (2) we bring in the H2 embedded database dependency. When H2 is found on the classpath, Spring Boot will autoconfigure Spring Data to use it as an in-memory data source. Configuring and working with Spring Data datasources is well documented on the project page.

With the new dependencies in place, the first thing we must do is start by modeling our microservice’s domain object: the user. The User class can be fairly simple for the sake of demonstration, and the code in Listing 5 shows a properly modeled JPA domain entity. We place this in the src/main/java/springpack/model/User.java class file within the project.

Listing 5

package springpack.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class User {
  private static final long serialVersionUID = 1l;

  @Id
  @GeneratedValue
  private Long id;

  @Column(nullable = false)
  private String username;

  @Column(nullable = false)
  private String email;

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }
}

We can make use of the javax.persistence.* annotations since Spring Data is now on the project’s compile-time classpath. Spring Boot makes it a seamless process to get up-and-running with data access objects, so we can model our DAO around the Repository service type, as afforded to us by Spring Data. Since our API follows relatively straight-forward CRUD operations, we can utilize the CrudRepository fixture provided by Spring Data to minimize the code necessary for the UserRepository DAO implementation.

Listing 6

package springpack.model;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {

  User findByUsername(String username); (1)
}

Amazingly, the UserRepository DAO implementation shown in Listing 6 is all that is necessary for us to have a fully formed service layer for our User domain object. The Repository interface offered by Spring Data allows us to create "helper" lookup methods based on the convention of the entity we are searching against. Based on the requirements, we know that our API layer will need to lookup users by their username, so we can add the findByUsername method at <1>. We place the UserRepository into the src/main/java/springpack/model/UserRepository.java class file within the project.

Before we can dig in to modifying the API to make use of the UserRepository, we first must define our Spring Boot application class. This class acts as a configuration entry point into the Spring Boot autoconfiguration engine and constructs a Spring ApplicationContext that we can use as a registry backing within our Ratpack application. Listing 7 depicts the Spring Boot configuration class.

Listing 7

package springpack;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class SpringBootConfig {

  @Bean
  ObjectMapper objectMapper() { (1)
    return new ObjectMapper();
  }
}

The stunningly small amount of code required for our SpringBootConfig class goes into the src/main/java/springpack/SpringBootConfig.java class file. In this, we are explicitly wiring in a bean definition for the Jackson ObjectMapper. We’ll use this within our API layer to read and write JSON.

The @SpringBootApplication annotation does the majority of the heavy lifting here. When we initialize the Spring Boot registry backing, we provide this class as the entry point. Its infrastructure will then use that annotation to scan the classpath for any available components, autowire them into the application context, and autoconfigure them according to the conventional rules of Spring Boot. For example, the mere presence of the UserRepository class (annotated with @Repository) on our application’s classpath will cause Spring Boot to proxy that interface through the Spring Data engine, which will also be configured to work with the H2 embedded database, which is also on the classpath. At this point, nothing more is needed from the Spring Boot side of things.

The next thing that we must do before we can implement our API layer is instruct Ratpack to use our Spring Boot application as a registry. Ratpack’s Spring Boot integration provides a fixture to seamlessly translate a Spring Boot application to a registry backing, making it a single line of code to merge the two worlds. The code in Listing 8 shows an updated main class, this time with the SpringBootConfig class stood up as a registry for our API layer.

Listing 8

package springpack;

import ratpack.server.RatpackServer;
import ratpack.spring.Spring;
import springpack.config.SpringBootConfig;

public class Main {

  public static void main(String[] args) throws Exception {
    RatpackServer.start(spec -> spec
      .registry(Spring.spring(SpringBootConfig.class)) (1)
      .handlers(chain -> chain
        .prefix("api/users", pchain -> pchain
          .prefix(":username", uchain -> uchain
            .all(ctx -> {
              String username = ctx.getPathTokens().get("username");
              ctx.byMethod(method -> method
                .get(() -> ctx.render("Received request for user: " + username))
                .put(() -> {
                  String json = ctx.getRequest().getBody().getText();
                  ctx.render("Received update request for user: " + username + ", JSON: " + json);
                })
                .delete(() -> ctx.render("Received delete request for user: " + username))
              );
            })
          )
          .all(ctx -> ctx
            .byMethod(method -> method
              .post(() -> {
                String json = ctx.getRequest().getBody().getText();
                ctx.render("Received request to create a new user with JSON: " + json);
              })
              .get(() -> ctx.render("Received request to list all users"))
            )
          )
        )
      )
    );
  }
}

The only change necessary is at (1), where we provide the Ratpack application definition with an explicit Registry implementation. Now we can get started implementing the API layer.

As you follow through on the upcoming changes, it is again important to understand that Ratpack differs a great deal from traditional servlet-based web applications. As noted earlier, Ratpack’s HTTP layer is built on a non-blocking network interface, which supports its nature as a highly performant web framework. A servlet-based web application will spawn a new thread for each incoming request, which is resource inefficient, but allows each request processing flow to act in isolation. In this paradigm, a web application is able to do things like make calls to a database and wait for the corresponding results without worrying about (relatively) impacting its ability to service subsequent clients. In a non-blocking web application, the networking layer does not block while the client or server is not sending data, so a high number of concurrent requests are able to be made across a small pool of "request taking" threads. It means, however, that if the application code were to block on one of these request taking threads, throughput would suffer dramatically. To that extent, it is important that blocking operations, like calls to a database, do not take place within the request thread.

Luckily, Ratpack makes it easy to work with blocking operations within your application by exposing a blocking interface on the request’s context. This will schedule blocking operations to a different thread pool, and allow those calls to complete synchronously, while still servicing new incoming requests at a high volume. Once the blocking call is complete, the processing flow will return to the request taking thread, and a response can be written back to the client. As we build out the API layer, we need to ensure that any calls that make use of the UserRepository are routed through the blocking fixture, as shown in API layer’s implementation in Listing 9.

Listing 9

package springpack;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import ratpack.exec.Promise;
import ratpack.handling.Context;
import ratpack.server.RatpackServer;
import ratpack.spring.Spring;
import springpack.model.User;
import springpack.model.UserRepository;

import java.util.HashMap;
import java.util.Map;

public class Main {
  private static final Map<String, String> NOT_FOUND = new HashMap<String, String>() {{
    put("status", "404");
    put("message", "NOT FOUND");
  }};
  private static final Map<String, String> NO_EMAIL = new HashMap<String, String>() {{
    put("status", "400");
    put("message", "NO EMAIL ADDRESS SUPPLIED");
  }};

  public static void main(String[] args) throws Exception {
    RatpackServer.start(spec -> spec
      .registry(Spring.spring(SpringBootConfig.class))
      .handlers(chain -> chain
        .prefix("api/users", pchain -> pchain
          .prefix(":username", uchain -> uchain
            .all(ctx -> {
              // extract the "username" path variable
              String username = ctx.getPathTokens().get("username");
              // pull the UserRepository out of the registry
              UserRepository userRepository = ctx.get(UserRepository.class);
              // pull the Jackson ObjectMapper out of the registry
              ObjectMapper mapper = ctx.get(ObjectMapper.class);
              // construct a "promise" for the requested user object. This will
              // be subscribed to within the respective handlers, according to what
              // they must do. The promise uses the "blocking" fixture to ensure
              // the DB call doesn't take place on a "request taking" thread.
              Promise<User> userPromise = ctx.blocking(() -> userRepository.findByUsername(username));
              ctx.byMethod(method -> method
                .get(() ->
                  // the .then() block will "subscribe" to the result, allowing
                  // us to send the user domain object back to the client
                  userPromise.then(user -> sendUser(ctx, user))
                )
                .put(() -> {
                  // Read the JSON from the request
                  String json = ctx.getRequest().getBody().getText();
                  // Parse out the JSON body into a Map
                  Map<String, String> body = mapper.readValue(json, new TypeReference<Map<String, String>>() {
                  });
                  // Check to make sure the request body contained an "email" address
                  if (body.containsKey("email")) {
                    userPromise
                      // map the new email address on to the user entity
                      .map(user -> {
                        user.setEmail(body.get("email"));
                        return user;
                      })
                      // and use the blocking thread pool to save the updated details
                      .blockingMap(userRepository::save)
                      // finally, send the updated user entity back to the client
                      .then(u1 -> sendUser(ctx, u1));
                  } else {
                    // bad request; we didn't get an email address
                    ctx.getResponse().status(400);
                    ctx.getResponse().send(mapper.writeValueAsBytes(NO_EMAIL));
                  }
                })
                .delete(() ->
                  userPromise
                    // make the DB delete call in a blocking thread
                    .blockingMap(user -> {
                      userRepository.delete(user);
                      return null;
                    })
                    // then send a 204 back to the client
                    .then(user -> {
                      ctx.getResponse().status(204);
                      ctx.getResponse().send();
                    })
                )
              );
            })
          )
          .all(ctx -> {
            // pull the UserRepository out of the registry
            UserRepository userRepository = ctx.get(UserRepository.class);
            // pull the Jackson ObjectMapper out of the registry
            ObjectMapper mapper = ctx.get(ObjectMapper.class);
            ctx.byMethod(method -> method
              .post(() -> {
                // read the JSON request body...
                String json = ctx.getRequest().getBody().getText();
                // ... and convert it into a user entity
                User user = mapper.readValue(json, User.class);
                // save the user entity on a blocking thread and
                // render the user entity back to the client
                ctx.blocking(() -> userRepository.save(user))
                  .then(u1 -> sendUser(ctx, u1));
              })
              .get(() ->
                // make the DB call, on a blocking thread, to list all users
                ctx.blocking(userRepository::findAll)
                  // and render the user list back to the client
                  .then(users -> {
                    ctx.getResponse().contentType("application/json");
                    ctx.getResponse().send(mapper.writeValueAsBytes(users));
                  })
              )
            );
          })
        )
      )
    );
  }

  private static void notFound(Context context) {
    ObjectMapper mapper = context.get(ObjectMapper.class);
    context.getResponse().status(404);
    try {
      context.getResponse().send(mapper.writeValueAsBytes(NOT_FOUND));
    } catch (JsonProcessingException e) {
      context.getResponse().send();
    }
  }

  private static void sendUser(Context context, User user) {
    if (user == null) {
      notFound(context);
    }

    ObjectMapper mapper = context.get(ObjectMapper.class);
    context.getResponse().contentType("application/json");
    try {
      context.getResponse().send(mapper.writeValueAsBytes(user));
    } catch (JsonProcessingException e) {
      context.getResponse().status(500);
      context.getResponse().send("Error serializing user to JSON");
    }
  }
}

The most noteworthy element in the API layer’s implementation is the use of the blocking mechanism, which can be extracted from the Context object that comes through with each request. When calling ctx.blocking(), a Promise object is returned, which must be subscribed to in order to have the code execute. This allows us to stage a promise, as shown in the prefix(":username") chain, for reuse within different handlers, keeping the code clean.

Now that the API is implemented, we can again run a series of curl tests to ensure that the microservice is working as intended:

Following this sequence of commands, we can indeed see that our API layer is functioning properly, and we have a fully formed, data-driven Ratpack and Spring Boot microservice that is using Spring Data JPA!

The final step of the whole process is to prepare it for deployment. The easiest way to accomplish this is to execute the gradle installDist command. This will package your application and all of its runtime dependencies in tarball (.tar file) and zip (.zip file) archives. It will additionally create cross-platform start scripts for you to be able to start your microservice on any system that has Java 8 installed. After the installDist task completes, you can find these archives in the build/distributions directory of your project.

Through this post you have learned how to create a microservice application that builds the performance and ease of use of Ratpack, while leveraging the vast ecosystem offered by Spring Boot. You can use this example as a lift-off point on your journey to building cloud native and data driven microservices on the JVM.

Happy Ratpacking! Happy Spring Booting!

About the Author

Daniel Woods is a Technology Enthusiast specialising in enterprise Java, Groovy, and Grails development. He has over a decade of experience in JVM software development, and shares his experience by contributing to open source projects like the Grails and Ratpack web frameworks. Dan has been a speaker at the Gr8conf and SpringOne 2GX conferences, where he presents his expertise in enterprise application architecture on the JVM.

Rate this Article

Relevance
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread
Community comments

Isn't micro-services already out of fashion? by peter lin

Anyone recommending Spring today is clearly drank too much Koolaid. Spring today isn't what it used to be. Spring WS is horrible compared to other toolkits.

Re: Isn't micro-services already out of fashion? by Dan W


Spring today isn't what it used to be. Spring WS is horrible compared to other toolkits.


I'm not completely sure how Spring WS fits in here. We're talking about building webapps, not SOAP endpoints.

Re: Isn't micro-services already out of fashion? by Raffi Basmajian

Spring today isn't what it used to be. Spring WS is horrible compared to other toolkits.


Not sure what "horrible" means without justifying your statements; you're not making much sense.

Besides, Spring WS is old SOAP tech. Spring MVC, REST, Springboot is light years ahead of WS, and very relevant today.

Re: Isn't micro-services already out of fashion? by Mark N

Spring is better than it used to be :)

I've never used Spring WS. I have used Spring Remoting (and still do). It allowed me to dump a ton of my own code and did it in much better way.

Is this the real peter lin? It does not sound like him. ;)

Re: Isn't micro-services already out of fashion? by peter lin

You're right Spring WS isn't what the article is about. But Spring today sucks. the mix of xml and annotation based dependency injection configuration is a total mess. Before annotation was introduced, configuring Spring was easier. Using the latest spring to configure SOAP and REST services sucks. The way Spring uses Jackson to handle JSON is stupid. The docs and examples are horrible, wrong and out-of-date. Most containers support DI now, so using spring is basically pointless.

it's like after Spring was bought out, the quality took a steep dive.

Re: Isn't micro-services already out of fashion? by Rashid Jilani

I agree with Peter. Now Spring is at a point where EJB used to be 10 yrs ago. Over complex, bloated and unnecessary complex.

Re: Isn't micro-services already out of fashion? by peter lin

haha... thanks for the laugh. Spring remoting, cuz RPC is back in fashion now?

joking aside, I hope RPC style stuff doesn't come back in fashion.

Re: Isn't micro-services already out of fashion? by Josh Long

Hi Peter / Rashid,

I'm Josh. I work on the Spring team (spring.io/team/jlong).

I'm concerned that either:

a) your observations are valid in which case it'd be nice to discuss how to improve things, or at least understand what your ideal alternative solution is.

or

b) your observations are dated and that you just need an updated perspective. I'd LOVE to give you a quick demo to that end. My basis for feeling you might just need an updated perspective, however misguided, is that you're referencing Spring WS and dependency injection. I don't know anybody using Spring for DI alone.

Either way, I'd like to try to help. my (personal) goal is to win the hearts and minds of passionate people like yourselves or figure out why I can't.

Regarding some (but not all) of your points:

- what do you use to do REST? I quite fancy modern Spring-based REST services. I can fit them in a single Tweet :) But, if you prefer JAX-RS we support that as well (see start.spring.io), or RatPack (which is what Dan's introducing above), so hopefully you don't feel too constrained.
- i'm sorry the docs and examples are horrible. have you tried spring.io/guides? We try to keep those up-to-date/working..
- using Spring for just DI is.. well it's pointless, as you say. DI is a blank slate. If you're working with modern Spring Boot then the goal is to write as little code that is aware of DI/configuration (be it in XML, Java config, or Java annotations) as possible.

I'm jlong@pivotal.io and I'm available all day / any day. Send me an email? Maybe we could do a google hangout or Skype call? I bet 10-30 minutes would be enough time to get somewhere. Thanks in advance for your time, gents.

Re: Isn't micro-services already out of fashion? by peter lin

Since you took time to respond, I'll just list the top pain points with Spring REST.

1. the way REST MVC works, it doesn't handle complex JSON poorly. We have an object that has several lists and the JSON it was sending to javascript calls wasn't formatted properly. To get around that, he had to use an objectmapper like this
new ObjectMapper().writeValueAsString(response)

Having used a variety of JSON libraries from plain servlets, that feels like a cludge hack. I've used GSON and Jackson without spring and didn't have these stupid issues.

2. the way Spring handles REST with @RequestParam is stupid. Specially how it handles missing request parameters or parameters with empty value is not well documented in the official docs on in the spring.io examples. Yes, we looked at them, but frankly the docs suck. To be fair, lots of open source projects have horrible docs. As an apache committer, I've committed the same sins. The docs I've written for apache are more detailed with explanations for common errors. It's things like this that make docs crap or useful.

3. in general, the sample code in spring.io and official docs are too sparse to be of any use. It's like the author assumes the reader and magically read their mind.

There are more pain points, but I've already garbage collected most of it from my brain. The last time I used Spring was before the introduction of annotations and it wasn't nearly as tricky to configure. Compared to writing REST with GSON and plain servlets, Spring REST is just a pile of unnecessary bloat.

Thanks for taking time to respond to my flame.

Re: Isn't micro-services already out of fashion? by Josh Long

1. JSON is a function of the HttpMessageConverter that's in play. Switch it out and you'll get different behavior. In particular, Jackson is a common default but you could also use GSON (or Google Protocol Buffers, or XML, or whatever.) Or provide your own instance of the HMC for Jackson to configure things like the ObjectMapper or custom modules.

2. The docs are... exhaustive.. and so theycan be a bit overwhelming for the uninitiated. We try to be as through as possible. We hoped that the spring.io/guides would prove a useful starting point for people who want task-focused introductions and the exhaustive docs a good fallback if the 80% case isn't clear. If the docs don't cover a specific point, we'd love to improve the docs. (although, here, a JIRA issue or a GH issue would be really helpful..)

So... do you have about 30 minutes for a quick Skype call?

Re: Isn't micro-services already out of fashion? by Sam BESS

I'am not a Spring user, nor a particular fan of it. But you keep talking about "fashion", and you sound more like an ignorant troll more than anything else.
Kthxbye

Re: Isn't micro-services already out of fashion? by peter lin

yup, I am an ignorant troll. I did admit I the post was a flame.

I've used Spring from the early days when Pico container existed along with a few other DI frameworks. I use the term fashion for a simple reason, all of these things are tools. I've used a ton of them on a variety of servlet and ejb containers. Sometimes it helps and other times it makes development slower.

When management types declare "everyone must use tools x", it is no longer about using a tool because it solves the specific problems. It becomes a decree because tool X is perceived to be popular. There was a time when I was productive with Spring, despite poor integration with Eclipse and other IDE. I moved away from Spring around the time annotations was being introduced. Even though I like using annotations, but sometimes it means configuration changes might require a full dev/test/qa cycle. At big corporations, that means a simple configuration change is no longer a quick redeploy.

Pre-annotations, a lot of people complained the XML configuration files were getting "out of hand", so Spring added it. As a consultant, I see large companies adopt things without thinking things through, simply because it is popular. In a different environment with different stack, I'm sure the pain of using the latest spring framework is probably lower. Like for example, if my customer was using TC server, it would probably just work out of the box without having to fight it.

so yes, I'm ignorant and I definitely haven't spent hundreds of hours reading spring code to figure out how it does it's "magic" over the last 10 years. I definitely didn't hear everyone on a project scream "why is spring fighting me and making my life hell?" I also didn't see first hand how bad the transaction manager is for complex transactions that involve several tables. I've never seen Spring jars collide with Websphere and unleash hell on a project.

Re: Isn't micro-services already out of fashion? by Mark N

I understand, but it is useful for some things. I have "desktop apps" and it works well as i have Java on both sides and a defined interface that i can code to. The problem with RCP or REST or WS is ... if only have one hammer ...

Re: Isn't micro-services already out of fashion? by peter lin

totally agree. In a real business environment, there's usually a mix of services written in different tools and languages. So far for what I do, which is 95% integration for customers, Spring is more headache than it's worth. In a greenfield settings where there's no legacy, mainframes, IBM/TIBCO/SAP, C/C++ and .Net, Spring can work and be productive. Sadly I rarely work on greenfield stuff where that freedom is an option.

I can see where micro-services can work, but for most of what I've seen with fortune 500 customers, something between monolithic and micro services is a better fit. The way Spring forces REST/SOAP services to be 1 class is terrible when compared to .Net WCF and Metro. Being able to logically group services that belong together in a class "can" make development and maintenance simpler. From my experience, the kinds of services I've seen typically have over 500 soap operations and several dozen endpoints. Doing this kind of development with micro-services approach feels like a bad fit to me.

As much as I would like to say "use the right tool for the right job", most of the time that isn't an option for me. That is the nature of consulting.

Re: Isn't micro-services already out of fashion? by Mark N

Sounds like you are in Hell. Hope you get paid well for it. :)

FYI, i have used Spring in those environments and it works - whereever it can be used. Where it cannot - well it will probably suck. I also use Spring with .NET and have for years (Spring.NET for Transactions, DDD, wiring Controllers, etc - just not its MVC). There is still nothing else as good in the .NET world. On that note, too bad NPanday is effectively dead (yeah, i know you have Maven but NuGet? ... sigh).

Is it perfect? No. But what is? You used .NET in an example. .NET has tons of pain too.

FYI, micro services does not mean, per se, REST or SOAP.

"From my experience, the kinds of services I've seen typically have over 500 soap operations and several dozen endpoints." I think you have pointed out the exact problem with "current" architecture. And the one that "microservices" is attempting to solve.

Re: Isn't micro-services already out of fashion? by peter lin

Agree 100% micro-services doesn't mean REST or SOAP, but it is popular. One could do micro services without frameworks and get some of the benefits of smaller components.

Spring can work, even in large complex environments, but the catch is the details. Change a few things in the stack, environment or components and it could make something into a total nightmare.

Integration is thankless work, but I learn way more than building greenfield stuff. When you're forced to integrate with legacy and new stuff, it really exposes where frameworks suck and where they help.

Re: Isn't micro-services already out of fashion? by Raffi Basmajian

Groovy DSL for DI, stopped using XML years ago. you need to keep up :-)

Re: Isn't micro-services already out of fashion? by mick love

Regardless of Peter's views, +1 to Josh for addressing them

try to bookmark by Abathur Abathur

try to bookmark

Re: Isn't micro-services already out of fashion? by Ravi Hasia

Peter sounds a little burnt and out of date. Seems like he couldn't come to terms with configuration via annotations after he had grasped how to do them via XML. Technologies evolve to demands. Just the nature of things.

+1 to Josh for addressing Peter's concerns (which do sound like a troll, whether they are or not, only Peter knows)

Is it possible to deploy RetPack + Spring boot application on tomcat? by ankit patel

Hi Daniel,

Thanks for the awesome article.

I just wanted to know that we have created a application using the RatPack + Spring Boot is it possible to deploy the application on tomcat or the application must be deploy on "Netty".

One subsequent question, if it is possible to deploy the application on tomcat then can we achieve the same through put while using "Netty"?

It seems strange by Brad Pickler

All this code seems complicated to read. It's a mess. Why do not separate?
Lots of unknown returning objects with hundreds of of method calls.

Re: It seems strange by Khoa Nguyen

Agree: the code is a mess.

Re: Is it possible to deploy RetPack + Spring boot application on tomcat? by Kaka Ronaldo

No. Ratpack by itself is a Http (web) server, why do you want to deploy Web server on another web server?. If you want to use Tomcat, just deploy plain springboot app on tomcat or even more easier thing to do is, just start Spring boot application with Spring-web dependency, the web server automatically chosen is Tomcat.

Simple answer is No, Tomcat cannot be used with netty, at least in this setup. But you can always have Tomcat features (if any) on top of Netty Non-blocking I/O, provided Tomcat has hooks to inject the network I/O layer of your choice.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

24 Discuss
General Feedback
Bugs
Advertising
Editorial
Marketing
InfoQ.com and all content copyright © 2006-2016 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT

We notice you’re using an ad blocker

We understand why you use ad blockers. However to keep InfoQ free we need your support. InfoQ will not provide your data to third parties without individual opt-in consent. We only work with advertisers relevant to our readers. Please consider whitelisting us.