I and our team need to get up to speed on Spring Boot-based REST development. It's easy enough to figure out the basics, but I care about the real world: varying development environments, services that need to be used outside of REST, easy-but-realistic test-driven-development, authorization, etc.
This repository is the result of me spending a month going through various granular tutorials and examples and putting together a template project that:
- Uses behavior driven development (Spock on JUnit) to dictate requirements for a CRUD-ish REST Web service
- Keeps "business logic" (e.g. who can edit/delete/view what) in the service and domain, not pinned on to the outside of a REST-only controller
- Uses JPA/Hibernate for persistence
- Allows authorization/security through Spring Security via request headers, focusing on JWT but leaving the door open to easy implementation of other security setups.
- Wraps any boilerplate/mundane/repetitive code in easy-to-use helpers or convenient base classes
The "master" branch of this repository will eventually contain the "complete" template. By cloning master, you'll get all of the code and the entire README covering what it does/how it works.
I've built each "evolution," such as adding on error handling or validation, as a new branch. In each branch, I'll update this readme, describing the goal of the branch and what's been added to the codebase to help us get there.
We'll be using Groovy. I started to do this in raw Java (since I haven't used it in a long time), but that's not where I want to go in life.
Our bare-bones REST service is based on the simple actuator-based REST example provided by Spring.
The following is what I've added on to make life easier.
The application.yml
declares spring.profiles.active
to be 'development'.
Why should you care?
This makes it so that:
- Developers can place universal configuration defaults in
src/resources/application.yaml
and let it be tracked in Git - We can .gitignore
src/resources/application-development.yaml
. Individual developments can then add any settings specific to their development environment (e.g. database usernames and passwords) in an optionalsrc/resources/application-development.yaml
.
(Production deployments would likely use an environment variable to point to their specific configuration file.)
Groovy's been added to the stock build.gradle file. The only changes this requires are:
- Applying the "groovy" plugin
- Adding Groovy as a compile dependency
This does mean a few things are different in the code: for example, we'll use Groovy-style no-arg default constructors instead of constructors with explicit argument lists.
The spring-loaded dependency has been added to the gradle file and hot reloading is ready to go. To use it (assuming you're a sane person with IntelliJ IDEA):
- Run the application with
gradle bootRun
, notjava jar
- If you're using a newer version of IDEA that allows the
compiler.automake.allow.when.app.running
setting to be set to true, do so then open IDEA preferences and go to Build, Execution, Deployment -> Compiler and check off "Make Project Automatically." After an IDEA restart, you should be good to go. - If you're not using a version where you can do that, just hit cmd/ctrl-F9 to rebuild the entire project (or map something easier to press to the "Rebuild Module" IDEA command).
Additionally, src/main/resources/application.properties is set up to not cache Thymeleaf, Freemaker, or Groovy templates.
In the Groovy/Grails world we favor Spock BDD over JUnit TDD. Therefore Spock and its Spring integration is added and working (there's some version hell to go through) and the unit test in the example has been rewritten to be a Spock specification.
The tests for the basic "Hello World" service in the Spring REST example contain a lot of boilerplate. To DRY things out, there's now an abstract class (AbstractRestConfigurationSpec) that provides dead-simple helpers for testing our REST services.
Basically, this:
@Test
public void shouldReturn200WhenSendingRequestToController() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.testRestTemplate.getForEntity(
"http://localhost:" + this.port + "/hello-world", Map.class);
then(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
Is now this:
def "Should return 200 when GETing /hello-world our service"() {
when:
ResponseEntity response = service.get( '/hello-world' )
then:
response.statusCode == HttpStatus.OK
}
Next we'll expand our little service to do common CRUD operations.
Greeting was a holdover from the original Spring tutorial. Much like Step 1, I'm basing this part off of a different Spring guide: Accessing Data with JPA. In it, we'll ditch Greeting and work instead with another simple domain class named Contact.
The first major addition to our template is the spring-boot-starter-data-jpa library. This lets us leverage Spring
Data's notion of a Repository
interface: basically, autogenerated methods like save(), delete(), and findOne(id). All
of Spring Data is a yuge topic, but it's worth reading the five minute quick start
to get the gist of what we're doing.
In our template, we're going to manage the proverbially overused Contact
example class. While we could automagically
handle everything for CRUDding a contact via REST by marking a ContactRepository with @RepositoryRestResource, that's
cheap and doesn't separate concerns.
Instead, we'll keep everything tidy by separating things into four classes:
domain.Contact
- Our Contact class, annotated to be persistable by any JPA implementor (e.g. Hibernate)
services.ContactService
- Coarse grained logic for managing contacts. Right now it's not much more than a wrapper
for the ContactRepository
, but if we wanted to do something like send alerts on contact deletion or e-mail
notifications, it'd be a good place to do so. It knows nothing about REST or however else we expose its logic.
repositories.ContactRepository
- tl;dr - Automagic DAO courtesy of Spring Data.
controllers.ContactController
- A REST-focused controller for Contact CRUD operations that maps different HTTP methods
and URI paths to different operations. It handles any marshalling of inbound data, serialization of outbound data, and
REST-specific response semantics, such as HTTP status codes.
As I built up tests against the initial service in Part 1 and especially in this part, I immediately grew frustrated with the Spring-provided RestTemplate (and TestRestTemplate). Since I do a good deal of front-end development, I'm used to simpler ways of calling REST services.
RestServiceHelper is my wrapper for RestTemplate that simplifies common REST requests.
It's not a huge deal, and I don't intend it to simplify everything, but the Groovy in me would much rather write this:
def response = restServiceHelper.get('/contacts', restServiceHelper.listOf(Contact) )
Than its RestTemplate raw-Java equivalent:
ResponseEntity<ArrayList<Contact>> response =
restTemplate.exchange( apiUrl + '/contacts',
HttpMethod.GET, null, new ParameterizedTypeReference<List<Contact>>() {
});
(Believe it or not, IDEA is good enough to sort out and code-hint the response).
For full use of the RestServiceHelper
, watch our Specs like ContactApiSpec
evolve or check out the Javadoc on
RestServiceHelper
and the jQuery .ajax() "options"-inspired SimpleRestRequest
it uses under the hood.
Up until now, we've lived in a happy world where the client-side (our specs for the ContactController) have behaved exactly as expected.
We all know that this isn't the real world: people are going to enter bad URLs for contacts and try to save contacts that aren't valid.
Let's see how Spring can help us deal with these.
We can handle really simple errors, like user searching for a contact that doesn't exist, by sending an appropriate status code and sensible response.
In this branch, I've started refactoring our one "happy path" specification (ZeroToHeroConfigurationSpec). I've renamed it ContactApiSpec, and I'm starting to move HTTP-method specific cases (like GET for a single Contact) into their own specifications so that we can more granularly deal with its nuances (like handling a bad ID parameter).
The ContactController's /contact/:id handler changes a little bit: it queries the ContactService for a contact. If it's non-null, it hands the contact back with a 200 - OK. If null, it sends an empty response with a 404 - NOT FOUND.
Boom, easy.
Let's get make life more complicated.
Most of the documentation and tutorials I've read about handling validation go like this:
- Slap some annotations on bean properties
- Add @Validated to your controller method's @RequestBody
- Party
IMO, that's cheap and naive for real-world use. The author of Spring's documentation agrees:
validation should not be tied to the web tier
...but also agrees that it's arguable...
There are pros and cons for considering validation as business logic
...so I'm going to walk through both cases.
This is the cheap and dirty way. It'll work for our simple case. My view of it is that it's entirely appropriate to do this whenever the structure of what's sent by the client doesn't match up with your domain model (frequently in anything non-trivial!). (For the Grails-y amongst us: think Commands.)
Knocking together validation at the Web tier is easy.
First, we annotate our bean:
@NotNull(message="contact.firstname.notnull")
String firstName
Then, we tell our controller that its input should be @Validated:
ResponseEntity<Contact> post( @Validated @RequestBody Contact contactDto ) {
And there. We can test immediately for a 400 - BAD REQUEST:
def "POSTing an invalid contact returns a 400 - BAD REQUEST"() {
when:
ResponseEntity<Contact> response = service.post(
'/contacts',
new Contact(
firstName: null,
lastName: "Berry"
)
)
then:
response.statusCode == HttpStatus.BAD_REQUEST
}
However, that's terrible for the client: Spring's default behavior will be to send them back an empty Contact while giving no indication of what was wrong.