Jan 2, 2017
Start with the Spring Initializr web site at
http://start.spring.io.
The Initializr will help you set up your Spring Boot project. By filling out just a few fields,
you can generate and download a zip file that contains (almost) everything you need to get started.
Don’t worry, the Initializr is not a code generator in the traditional sense, but more of a
generator for initial project scaffolding.
In the Dependencies field, type in: web, actuator, jpa, h2
and devtools
. If you hit Enter after
each tag is matched, it will be permanently selected. Feel free to customize the Group
and
Artifact
coordinates as you see fit but keep the default Spring Boot Version.
Don’t be afraid to click “Switch to the full version” and explore the other Spring Boot options.
You may even choose to start your project with a Gradle build configuration instead (however, I will
assume Maven for the remainder of this journal).
Once the dependencies selected, click the the Generate Project button. When the download finishes,
unzip the file and import the project into your development environment. I primarily use the
IntelliJ IDE from JetBrains, so some of the steps that I describe
may not match exactly, but they should be close.
Run the application.
Depending on your environment, the Run profile might be automatically configured for you. If not,
just navigate the project source folders until you find the default Application class and run it
manually. If everything goes well, you should see several colorful INFO messages logged out to the
console and finally a Tomcat instance listening on the default port of 8080.
Try it out at: localhost:8080
You should see the default “White label” error page in your browser. Don’t worry, you haven’t done
anything wrong, this is the default Spring Boot error page. The Initializr only generates just
enough code to get you started and this is the expected result.
If you use the unix curl
command instead, you will see a JSON error response. This is Spring Boot
being smart about the requested Content Type. By default, Boot will return the most appropriate
type in the response.
Ultimately, we may customize this behavior but for now, lets write some code.
Jan 9, 2017
Lets start by creating a domain object. By a domain object, I mean an object that we will use to
represent the business entity that we want to model. To do this, we will use a design technique
called Domain-Driven Design. The Wikipedia entry for
Domain-Driven Design states:
Domain-driven design (DDD) is an approach to software development for complex needs by connecting the implementation to an evolving model.
That sounds quite abstract, but in practice, it will become apparent that DDD is just a set of
concepts that are technology agnostic and help to provide a ubiquitous language that can be used
solve many types of complex problems.
You are not required to be an expert in DDD, but it does help to understand the basics. The
Wikipedia article linked above is a great start, but for more information, please refer to Eric
Evans’ excellent book: Domain-Driven Design: Tackling Complexity in the Heart of Software.
Or, for the more impatient reader Domain-Driven Design Distilled
by Vaughn Vernon.
The Patient Entity
In DDD, an Entity is a special type of domain object that has a unique, unchangeable identity that
when combined with mutable attributes, can be used to express a model.
In the the domain of the Pain Management application, we need to represent a Patient. This would
appear to map well to the @Entity
annotation defined by the standard
Java Persistence API (JPA). However, it is
important to note that, from a design perspective, we should resist coupling an Entity with the
(sometimes) cumbersome details of the underlying storage technology.
Initially, don’t worry about schemas, columns or indexes. We will deal with those concerns when it
becomes appropriate. For now, just worry about the details of the identity and attributes that we
need to define a Patient.
To create the Patient Entity, first create a domain
package under the default package name that
you used for the project. Then create a new Java Class called Patient
and add the following code:
@Entity
public class Patient {
@Id
@GeneratedValue
private Long id;
}
This is really the simplest Entity that we could define using JPA.
The Patient’s identity (id) is a synthetic value that is arguably a poor choice for representing the
concept long term. However, the choice is intentional as it is an immediate convenience but we will
be able to refactor it later.
DDD “purists” may dislike this approach, arguing that the JPA annotations immediately couple the
technology to the domain. In general, I tend to be more pragmatic on this topic, particularly when
using Spring Data as it helps balance design and development with the right level of abstraction and
still get things done.
Save and restart the application.
This time, if you watch the logs closely, you should see additional messages from Hibernate.
Hibernate is Spring Boot’s default JPA implementation and, because we have an @Entity defined, it is
able to act on our behalf. In fact, Hibernate automatically defined an initial schema and created a
database table for us, but how?
When we originally created the project, we chose the H2
database and, by including it’s
dependency, Spring Boot will automatically activated it in-memory during local development.
Hibernate uses H2 and will execute the commands to create a table per the @Entity definition. This
is a very fast and convenient way to get your design ideas realized without having to immediately
fuss with storage details.
H2 also comes with a very handy web console that Spring
Boot can launch. If your application does not have the console, you may need to add a property to
enable it in the default application.properties
file:
spring.h2.console.enabled=true
I have noticed that the H2 console is automatically enabled if you also have the devtools
dependency in your project (this may be unintended so be aware of the configuration option above).
Also, make sure you set the JDBC URL to jdbc:h2:mem:testdb
instead of the console default or you
won’t see the PATIENT table.
As mentioned earlier, do not get too concerned with the underlying database technology. For the
time being, we will let Hibernate decide how best to define and manage the database schema. In this
way we initially get the flexibility and agility that can come with using so called “schemaless”
systems.
For now lets focus on the basics of interacting with the Patient entity.
Jan 16, 2017
A Repository is another DDD concept that is used to describe an abstraction that mediates the domain
model from data mapping layers.
With Spring Data, we use an @Repository
annotation to wrap common persistence operations for a
specific Entity type and, hopefully, keep the underlying storage details out of the domain.
To create a Patient Repository, first create a package called repositories
in the base
package and then create a PatientRepository
Java interface. Finally, add the following code to
the interface:
@Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
}
Surprisingly, that’s not much code. But what does it actually do?
By extending the JpaRepository class we are creating a custom Spring Data Repository for the Patient
Entity that will be initialized with a JPA entity manager. It is a powerful abstraction in Spring
Data that also provides several default CRUD operations and a custom query system that we will
discuss later in more detail.
When Spring manages the Repository, it can generate JPA queries based on the Entity definition that
it is typed to and we will rarely have to deal with JPA directly.
// TODO: talk about why I’m not using @RestRepositories. In short, I find Spring Data REST to be
a little too opinionated and harder to customize.
Before we get too far ahead of ourselves, lets enable some basic configuration options that will
help with future debugging:
Edit the default application.properties
file in the src/main/resources
folder and add:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
I do STRONGLY recommend adding these properties and keeping them set for the duration of your
local development. You will frequently need to review the SQL that is being created and executed by
Hibernate.
Jan 23, 2017
Create a package called controllers
in the application package and then create a
PatientController
class within it. Add the following code to the class:
@RestController
public class PatientController {
private final PatientRepository patientRepository;
@Autowired
public PatientController(PatientRepository patientRepository) {
this.patientRepository = patientRepository;
}
@GetMapping("/patients/{id}")
public Patient getPatient(@PathVariable("id") Long id) {
return patientRepository.findOne(id);
}
}
Restart the application.
When the application starts, you should be able to make “RESTful” GET calls on the /patients
resource. Let’s try one out at: http://localhost:8080/patients/1.
However, nothing is displayed in the browser, the HTTP response is just an empty page (and that
result seems a bit odd). Intuitively you should already be thinking that there can’t be a resource
at “patients/1” because we haven’t created it yet (and you would be correct). However, our
expectation should also be that when a resource is not found, we should receive a 404 NOT FOUND
error as a response.
The answer to this little puzzle lays in the nuanced default behavior of the built-in findOne
implementation. From the Spring documentation,
we see that the Returns block for this method says:
the entity with the given id or null if none found
Since the null result is happily passed back up the call stack, the response is processes as a
success with an empty payload.
Create a JPA ‘findBy’ Implementation
Lets crete a more appropriate method to the Repository interface, one that we can exert more control
over to get the desired behavior. In the PatientRepository, add a findById
interface method:
public interface PatientRepository extends JpaRepository<Patient, Long> {
Optional<Patient> findById(Long id);
}
Here we are leveraging the Spring Data findBy
query naming convention to produce a JPA query that
will fetch a Patient using the id
field. We’re also taking advantage of a Java 8 feature by
wrapping the Patient response with an Optional<>. This means that even null results returned by
the repository will be wrapped in a usable Object.
Finally, change the @GetMapping implementation in the Patient Controller to use the new query:
@GetMapping("/patients/{id}")
public Patient getPatient(@PathVariable("id") Long id) {
return patientRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Not found"));
}
The orElseThrows()
is a method call chained onto the Optional return type. This construct is null
safe and frees us from having to write (ugly?) null checking code. If there is a null response,
this method will throw a built-in Java exception with a message indicating that the requested
resource was not found.
Restart the application.
Use curl
this time on the /patients/1
resource:
curl -sS localhost:8080/patients/1 | jq
The result is a 500 status error code. [Sad Trombone].
Wait, I thought this approach was supposed to help? A 500 error code is even worse than the empty
200 because it implies that there is something wrong with the server. This could have real-world
implications if the application were being hosted behind a reverse proxy configured to remove
instances returning “unhealthy” response codes.
To be fair, this is partially my own fault.
For expediency, I chose to use an existing Java runtime exception and the Spring MVC response
handler treats it as an unhandled exception and returns a 500 response. There are multiple ways to
address this behavior but for now, we will create a new type of exception that Spring will handle
correctly.
// TODO Discuss Spring’s lack of standard HTTP error exceptions and why 500 exceptions are a poor
default behavior.
Create a Custom Exception
To fix this, create an exceptions
package in the application root and then add a Java class called
NotFoundException
with the following code:
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
public NotFoundException() {
super();
}
public NotFoundException(String message) {
super(message);
}
}
Modify the Patient GET method to throw this new exception instead of the original
IllegalArgumentException
:
return patientRepository.findById(id)
.orElseThrow(() -> new NotFoundException(String.format("Patient %d not found", id)));
Restart the application and attempt the curl
command again.
{
"timestamp": 1481488691203,
"status": 404,
"error": "Not Found",
"exception": "io.undertree.symptom.exceptions.NotFoundException",
"message": "Patient 1 not found",
"path": "/patients/1"
}
That looks much better! By marking the Exception with a @ResponseStatus
, the default exception
handler interprets the exception as a 404 and returns the appropriate error block. This is exactly
what we and our eventual clients should expect.
Jan 30, 2017
We have a functional GET but now we need a way to add a Patient. Using REST semantics this should
be expressed with a POST. To support this, add the following code to the PatientController:
@PostMapping("/patients")
public Patient addPatient(@RequestBody Patient patient) {
return patientRepository.save(patient);
}
Restart and issue a curl -H "Content-Type: application/json" -X POST localhost:8080/patient -d '{}'
Interestingly we get a “{}” back. If we look at the logs we should be able to see that an insert
did take place but where is the id of the entity we just created?
If we open up the H2 console again, we should see that a row was created with the ID of 1. If we
use our GET operation, we should get the entity back right?
curl -sS localhost:8080/patients/1 | jq
Still the same empty empty JSON object?
The reason we don’t see the id field is that we failed to provide a getter method on the entity
class. The default Jackson library that Spring uses to convert the object to JSON needs getters
and setters to be able to access object’s internal values.
Modify the Patient class to add a getter for the id field:
public Long getId() {
return id;
}
Now when you execute the POST we can see an id field on the JSON object
curl -sS -H "Content-Type: application/json" -X POST localhost:8080/patients -d '{}' | jq
// TODO discuss Lombok
Feb 6, 2017
Our basic Patient domain object is not very exciting and lacks the typical data
elements that one might expect. If we do a little research on sites like
schema.org, we can see that there are definitions
already out there for a Patient.
Let’s use some of that information to start filling out the domain.
Make the Patient model a bit more… interesting
Add the following attributes to the Patient entity (and Add the associated
getter/setters using your IDEs code generator if possible):
private String givenName;
private String familyName;
private LocalDate birthDate;
Using curl again submit a Patient add request:
curl -sS -H "Content-Type: application/json" -X POST localhost:8080/patients -d '{"givenName":"Max","familyName":"Colorado","birthDate":"1942-12-11"}' | jq
400 Error?
So the LocalDate type is causes a JsonMappingException. We need to add a
Jackson module dependency to the project so it knows how to handle JSR 310
Dates (introduced in Java 8).
Add this dependency to the project pom.xml
:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Refresh the dependencies and restart the application (Spring Boot will
automatically register this module with the default Jackson ObjectMapper).
Now if we execute the curl
again, we see an odd looking date structure:
"birthDate": [
1942,
12,
11
]
While an accurate date, that isn’t exactly the JSON structure what we want.
There is yet another configuration change we need here to tell Jackson to
format the date correctly.
Update the application.properties
in resources and include the property:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
This somewhat confusingly named configuration option tells Jackson to output
Dates in ISO-8601 textual value.
This is probably the configuration that you’ll want to use in most
applications.
But wait.
If we look at how the date was actually stored in the database through the H2
console, it looks like a BLOB. That isn’t a good choice since we might want to
be able to query against it later.
// TODO: Discuss why JPA doesn’t support LocalDate?
Fixing the LocalDate Time Database Mapping
Okay, I lied. I said not to worry about the actual database implementation at
this point, but we are going to take this one detour.
In general, you shouldn’t normally need worry too much about the type mappings
until much later in the design process but this specific issue can be resolved
fairly easy during the initial project set up.
As of the time of writing, JPA does not natively support the JSR 310 dates.
Fortunately, Hibernate stepped in and provides an additional library to help
with managing the type conversions. You can read more about it here.
Thankfully, it is just as simple as adding the following dependency to your
pom.xml
:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
<version>${hibernate.version}</version>
</dependency>
This library will introduce the appropriate type converters that will deal with
the new date types in a more elegant fashion. When you refresh the
dependencies and restart the application, you will see in the logs that the
table is now being create with a proper DATE type:
Hibernate:
create table patient (
id bigint generated by default as identity,
birth_date date,
family_name varchar(255),
given_name varchar(255),
primary key (id)
)
The need for this additional library will eventually go away when Spring Boot
upgrades to Hibernate 5.2 (see the Hibernate Migration Guide 5.2).
Feb 6, 2017