URL Shortener (Part 2): Docker, Swagger, and Cache
URL Shortener (Part 2): Docker, Swagger, and Cache
Learn how to make your Java URL shortener even better by adding documentation with Swagger UI and containerizing your app with Docker.
Join the DZone community and get the full member experience.Join For Free
In my last post, I explained URL shortening services and how to implement one.
In this post, I will talk about Swagger documentation, dockerization of applications, application caches, and MySql scheduled events.
Every time you develop an API, it is good to document it in some way. Documentation makes APIs easier to understand and use. The API in this project is documented using Swagger UI.
Swagger UI allows anyone to visualize and interact with the API’s resources without having any of the implementation logic in place. It’s automatically generated, with the visual documentation making it easy for back end implementation and client-side consumption.
There are several steps that we need to do to include Swagger UI in the project.
First, we need to add Maven dependencies to the pom.xml file:
For reference, you can see the complete pom.xml file here.
After adding the Maven dependencies, it is time to add our Swagger configuration.
Inside the config folder, we need to create a new class - SwaggerConfig.java.
At the top of the class, we need to add a couple of annotations.
@Configurationindicates that a class declares one or more
@Beansmethods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.
@EnableSwagger2indicates that Swagger support should be enabled.
Next, we should add a Docket bean, which provides the primary API configuration with sensible defaults and convenience methods for configuration.
apiInfo() method takes the ApiInfo object where we can configure all necessary API information — otherwise, it uses some default values. To make our code cleaner, we should make a private method that will configure and return the ApiInfo object and pass that method as a parameter of an
apiInfo() method. In this case, it is the
apis() method allows us to filter packages that are being documented.
Now Swagger UI is configured and we can start documenting our API. Inside UrlController, above every endpoint, we can use the @ApiOperation annotation to add a description. Depending on your needs, you can use some other annotations.
A cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere.
The most frequently used type of cache is an in-memory cache that stores cached data in RAM. When data is requested and found in the cache, it is served from RAM instead from a database. This way, we avoid calling costly backend when a user requests data.
A URL shortener is a type of application that has more read requests than write requests, which means it is an ideal application to use caching.
After that, in the controller, we need to set the @Cachable annotation above the GET method. This annotation automatically stores the result of the method call to the cache. In the
@Cachable annotation, we set the value parameter which is the name of the cache and key parameter which is the cache key. In this case for the cache key, we are going to use 'shortUrl' because we are sure it is unique. The Sync parameter is set to true to ensure only a single thread is building the cache value.
And that is it — our cache is set and when we first load URL with some short link, the result will be saved to cache and any additional call to the endpoint with the same short link will retrieve the result from the cache instead of from the database.
Dockerization is the process of packaging application and its dependencies in a Docker container. Once we configure a Docker container, we can easily run our application on any server or computer that supports Docker.
The first thing we need to do is to create a Dockerfile. A Dockerfile is a text file that contains all the commands a user could call on the command line to assemble an image.
FROM - This is where we set a base image for the build base. We are going to use openjdk v13 which is a free and open-source version of Java. You can find other images for your base image at Docker hub which is a place for sharing images.
COPY - This command copies files from the local filesystem (your computer) to the filesystem of the container at the path we specified. So we are going to copy the JAR file from the target folder to /usr/src/app folder in the container. I will explain creating JAR file a bit later.
EXPOSE - Instruction that informs Docker that the container listens on the specified network ports at runtime. The default protocol is TCP and you can specify if you want to use UDP.
ENTRYPOINT - This instruction allows you to configure a container that will run as an executable. Here we need to specify how Docker will run out an application. Command to run an application from .jar file is
java -jar <app_name>.jar so we put that 3 words in an array and that is it.
Now when we have our Dockerfile we should build the image from it. But like I mentioned before, we first need to create a .jar file from our project so the COPY command in our Dockerfile can work properly. To create an executable .jar, we are going to use Maven. We need to make sure we have a Maven inside our pom.xml. If Maven is missing, we can add it
After that, we should just run the command
mvn clean package.
After that, we can build a Docker image. We need to make sure we are in the same folder where a Dockerfile is so we can run this command
docker build -t url-shortener:latest .
-t is used to tag an image. In our case, that means that the name of the repository will be url-shortener and the tag will be the latest. Tagging is used for versioning of images. After that command is done, we can make sure we created an image with the command
For the last step, we should build our images. I say images because we will also run MySQL server in a docker container. Database container will be isolated from the application container. To run MySQL server in docker container simply run
You can see the documentation on DockerHub.
When we have a database running inside the container, we need to configure our application to connect to that MySQL server. Inside application.properties, set spring.datasource.url to connect to the 'shortener' container.
Because we made some changes in our project it is required to pack our project into a .jar file using Maven and build the Docker image from the Dockerfile again.
Now that we have a Docker image, we should run our container. We do that with the command
-d means that a Docker container runs in the background of your terminal.
--name lets you set the name of your container
-p <host-port>:<docker-port> - This is simply mapping ports on your local computer to ports inside container. In this case, we exposed port 8080 inside container and decided to map it to our local port 8080
--link with this we link our application container with database container to allow containers to discover each other and securely transfer information about one container to another container. It is important to know that this flag is now legacy and it will be removed in the near future. Instead of links, we would need to create a network to facilitate communication between two containers.
url-shortener is the name of docker image we want to run.
And with this, we are done — in your browser, visit http://localhost:8080/swagger-ui.html
Now you can publish your image to DockerHub and easily run your application on any computer and server.
There are two more things I want to talk about to improve our Docker experience. One is multi-stage builds and the other is docker-compose.
With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.
Multi-stage builds are good for us to avoid manually creating .jar files every time we make some change in our code. With multi-stage builds, we can define one stage of the build that will do the Maven package command and the other stage will copy the result from the first build to the filesystem of a Docker container.
You can see the complete Dockerfile here.
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.
With docker-compose, we will pack our application and database into a single configuration file and then run everything at once. This way we avoid running MySql container and then linking it to the application container every time.
Docker-compose.yml is pretty much self-explanatory — first, we configure our MySQL container by setting the image to mysql v8.0 and credentials for the MySQL server. After that, we configure the application container by setting build parameters because we need to build an image instead of pulling it like we did with MySQL. Also, we need to set that application container depends on the MySQL container.
Now we can run the whole project with only one command:
MySQL Scheduled Event
This part is optional, but I think somebody might find this useful anyway. So in my previous post, I talked about the expiration date of the short link, which can be user-defined or some default value. For this problem, we can set a scheduled event inside our database. This event would run every x minutes and would delete any row from the database where the expiration date is lower than the current time. Simple as that. This works well on a small amount of data in the database.
Now I need to warn you about a couple of issues with this solution.
First - This event will remove records from the database, but it will not remove data from the cache. Like we said before, the cache will not look inside the database if it can find matching data there. So even if data no longer exists in the database because we deleted it, we can still get it from the cache.
Second - In my example script, I set that the event runs every 2 minutes. If our database becomes huge, then the event might not finish execution within its scheduling interval. The result may be multiple instances of the event executing simultaneously.
In this post, I added more details for my URL shortener project. Swagger UI, Docker containers, and scheduled events are all useful and popular for developing modern APIs.
Opinions expressed by DZone contributors are their own.