The big three cloud providers (AWS, Azure, and Google Cloud, in that order) have their various strengths and areas of expertise. Most large organizations though typically pick one cloud provider for their cloud computing needs. This works well if you’re a Java shop that’s on AWS, or a Microsoft shop on Azure. But what if you’re on a large Java project in an organization that wants to use Azure? You’re in luck.
Microsoft Azure has come a long way, and is very supportive of non-Microsoft technologies. The proof though is in the pudding. Which is where this blog post comes in. I take Josh Long’s Bootiful Microservice Services, a great starting point to get a cloud native Spring microservice application up and running, and show how it can be run on Azure.
This first blog post will be all about setting up our basic microservices by walking through the various parts of Josh’s example application, with some best practices and patterns that I’ve found to be effective. Rather than a simplistic ToDo application, we’ll be basing our application off of my favorite bagel shop in New York, Original Bagel Boss in Hicksville, to manage its orders, inventory, etc. If we can run a bagel shop on a Spring application running on Azure, and keep customers happy and full of carbohydrates, then it proves out for applications of a similar size and complexity.
We’ll be staying mostly inside the familiar Java confines, then slowly start working our way out to getting our application deployed to Azure. Then we’ll start introducing additional complexity like Spring Batch jobs, a React front end, etc. A setup this complex will show that Azure is ready for prime time when it comes to running applications in production, even if they are built on non-Microsoft technologies.
There are several different ways to start a Spring Cloud Native application. The first step would be to select an IDE. JetBrains’ IntelliJ IDEA is one of the most common ones across the Java ecosystem. In this document, I will show how Spring’s STS, their own variation on Eclipse, can be used to develop the application.
First thing’s first, is to download STS off of Spring’s website. I will be using the Windows 64 bit edition, version 4.2.0, installed at C:\Program Files\Spring\STS\4.2.0. Then make sure you have the right version of the JDK and JRE installed on your system. I’ll be using Java 8 in this example, with JRE and JDK at 1.8 161.
And, of course, make sure to switch STS over to the darkest theme you can find. No real programmer uses a light IDE.
I will be using the microservice paradigm with this application. It will allow for the application to be deconstructed and maintained along the lines of the bounded contexts with the domain. Each piece has loose coupling with other pieces, and has high cohesion within the bounded context. It is even possible to have each bounded context implemented using completely different technologies, but the recommendation is instead to keep high cohesion in terms of coding style, tools used, and even build and deployment strategies between the microservices.
The First Microservice: Orders
Getting the microservice up and running.
With that in mind, the next step is to go to start.spring.io to give me a starting point. I will create the first bounded context, an important Domain-Driven Design concept, for my application: Orders. I will use Maven, because it is the best choice. Java, rather than Kotlin or Groovy, the latest release Spring Boot. I decided to have it make JAR, not WAR, and use Java 8. The only dependency I added was the Web dependency, as I know I want to create a very simple API to begin with.
I extract the archive produced to my STS workspace, rename it to be
com.bagelboss.orders, and start the Import process. I click on File -> Import. Select Existing Maven Project.
I open the
com.bagelboss.orders folder, and select the pom.xml inside it to import.
Once it is imported, I then start the Spring Boot server via the Boot Dashboard.
Next, we’ll add Swagger to our API following this tutorial. Add the dependencies to the pom file, create the configuration class, and restart the app. This gives you an API definition available at /v2/api-docs, and a Swagger UI page available at /swagger-ui.html.
Then we’ll create a very simple API that takes in an order request with the desired number of bagels and a due date, and returns a canned Id for the order. When the API is invoked for this endpoint, whatever parameters are put in, the response is always the same canned order id of 1.
Next will be to add in a temporary persistence layer: H2. At first the H2 database will be in memory, then purged every reboot. Later in this guide, the persistence layer will truly be persistent, and live on even after the application has been rebooted. We will also switch the order id over to a UUID. Every other primary key generated by the microservices in this application will use the UUID paradigm as well.
Following the steps in this guide, a simple
OrderDetailEntity entity is added, along with the changes to the pom file.
The Spring Data JPA makes it very simple to create
OrderDetailRepository interfaces that are implemented at run time. This runtime implementation allows the developers to focus more on defining relationships than worrying about the implementation details of writing queries.
Updating and Inquiry APIs
Once the persistence is setup, the next step is to create an API that allows orders to be marked as fulfilled, as well as an API our customers can invoke to check on the status of their order. This is implemented in the
OrderService via the
getOrderStatus methods. The controller leverages the
@PathVariable annotation to make the interaction with the API very RESTful. A custom
OrderStatusEnumeration is used to represent the status of the order as it progresses through the bakery.
With a few different APIs in place, some of which should only be accessible by users with sufficient privileges, security is becoming a very real concern. This step will add Spring Security to our existing microservice, with an extremely simplistic authentication/authorization process.
This guide is a great starting point to adding in Basic Auth via Spring Security. Some of it isn’t needed for this example project, such as the authentication entry point. The only caveat needed for this section is to explicitly disable CSRF for the moment. It will be enabled once the React UI is built, but can be disabled now that Postman will be the primary way to interface with the APIs.
Of course, no microservice is worth its weight in digital salt without some sort of automated testing. This part of the guide will walk through getting both unit testing as well as integration testing in place for the code produced to this point.
This guide is a great starting point to see what needs to be done to add automated testing to the single microservice. I’ll start out with a unit test, then work up to an integration test. The
spring-boot-starter-test dependency was added in a previous step. Next, in src/test/java, add in a
com.bagelboss.orders.api package, with an
OrderRestControllerTest class for unit tests. Follow the convention in the article for their
EmployeeRepositoryControllerIntegrationTest. This makes use of Spring’s
MockMvc class, which makes it very easy to simulate how an incoming API request will flow through the application code.
The very simplistic test in the
OrderRestController_receiveOrder_passesBeanToService method creates a new
ReceiveOrderBean with an UUID for the expected order ID, invokes the API, ensures the response is a HTTP 200 OK, and interrogates the response to ensure it matches the expected order ID. To make the common job of serializing the Java object as a JSON string, the
TestHelper class was created with a static method to centralize the serialization to JSON. This unit test initially fails because there is no notion of authentication and authorization in the mock API request.
To rectify this, we’ll rename the method to
OrderRestController_receiveOrder_unauthorizedWithNoCredentials. The expected response will be
HTTP 401 Unauthorized. The new method will be
OrderRestController_receiveOrder_passesBeanToServiceWithCredentials. We’ll need to add the
spring-security-test Maven artifact in the test scope. This gives us access to the
@WithMockUser annotation, automatically appending on the stock Basic Auth credentials needed for this API.
With the credentials in place, the next error received when this test runs is that response, which is a JSON string containing a UUID, is not equal to an instance of the UUID class cast to a string. This makes sense because the response is JSON containing the UUID, and the equivalency test needs to be a UUID against a UUID, or a JSON string containing a UUID with a UUID serialized to a JSON string. In this scenario, we’ll convert the JSON string to an instance of the UUID class, and compare that to the expected UUID. Both unit tests now thankfully pass.
Adding unit tests for the additional methods in this controller follows similar paths. The
setOrderStatus API returns void, so instead, a verify needs to be placed on the mock
OrderService to ensure that it received the expected parameters. This can be done by leveraging the
verify() method along with the
ArgumentCaptor to assert that what was returned from the method matches the expectation.
The next method,
getOrderStatus, will have specific tests for when an order is not found throwing an exception, and the positive case of finding an order and returning its status.
At this point, the API is almost ready for prime time. The next step is to create an edge API using Zuul, allow for service discovery using Eureka, circuit breaker using Hystrix, log tracing and visualization using Sleuth and Zipkin, respectively, and a Spring Cloud Config to manage the configuration of these services. This guide will cover only one of the many ways to get this up and running.
Convert the existing Orders service to a git repo
The first step will be to take the existing code we’ve done for the
Orders service, and place it within a git repository. To do this, head over to Azure DevOps, create a new git repo.
Then clone it to your machine, move the code you have done to the repo, git add, git commit, and git push. My repository is published at https://dev.azure.com/zgardner0708/com.bagelboss.orders
Define a Spring Cloud Configuration service
The next step is to define a configuration service. This allows us to centrally manage the configuration (
application.properties) for all of the services that are deployed to our microservice mesh. This is the most effective way to manage so many different services with many different configuration variations.
To get this up and running, two different git repos are necessary:
com.bagelboss.configservice. The former is the git repo where we’ll manage the equivalent of the
application.properties for all of our services in our microservice mesh. The latter is the service which services in our microservice architecture will query to obtain their configuration from.
With both repos created, clone them to the local machine. Then head over to start.spring.io, create a Maven Java 8 project using the latest stable version of Spring. Add on Config Server as a dependency.
Download the archive, and extract it into the
com.bagelboss.configservice repo. Then import the existing Maven project into the workspace. It will automatically appear in the Boot Dashboard as a service that can be started.
Once imported, navigate to the application.properties. Set the server.port to be 8888, and the spring.cloud.config.server.git.uri to be https://email@example.com/zgardner0708/com.bagelboss.configuration/_git/com.bagelboss.configuration. This lets the config service know which port it should make itself available on, and which repository it should use to provide configuration values when asked by the other services.
Then we’ll create a simple configuration file for the
Orders service to read from. In
order-development.properties with a simple property of
customer.name.default and a value of “A customer name from configuration”. Git add, git commit, and git push to make it available.
Now its time to change the
Order service to read from the
Config service. Add the dependency in the pom.xml of the Order service for the group
org.springframework.cloud and the artifactId of
spring-cloud-starter-config. Then remove the
application.properties, and add
bootstrap.properties. This is because the bootstrap file needs to be read in before the application.properties, and there will be no application.properties local to the service. The bootstrap.properties needs a spring.application.name of order, a spring.profiles.active of development, and a spring.cloud.config.uri of http://localhost:8888.
Then, start up the
Config service. Note that this will need to be the first service started, as all of the other services will pull their configuration from it. Once launched, http://localhost:8888/order-development.properties should resolve to a properties file that was configured in the
com.bagelboss.configuration git repo.
Before launching the
Order service again, the
OrderService class will be tweaked to pull the
customer.name.default value from the properties. As this property value only exists from the
Config service, this will prove that everything is configured correctly. Start up the
Order service in debug mode, put a breakpoint on the line where the default customer name will be used, invoke the
receiveOrder API, and observe that the value of the default customer name matches what was configured in the
Wrapping it up, looking ahead
In this blog post, we’ve gotten a Spring cloud native microservice application running in our IDE. The source control is the only thing right now that is using Azure. In the next blog post, I’ll walk through how to leverage Azure DevOps for building the microservices in the application. Then I’ll show how to use AKS (Azure Kubernetes Service) to orchestrate the microservices that make up this application.
Additional blog posts will cover advanced functionality such as Spring Batch integration, a message queue/bus, and eventually a React UI to make the whole thing tangible.