DEV Community

loading...
Cover image for Url shortener pt.II - dockerization and other stuff

Url shortener pt.II - dockerization and other stuff

antemarin_53 profile image amarin Updated on ・9 min read

Introduction

In my last post I explained what is URL shortening service and how to implement one.

In this post, I will talk about Swagger documentation, dockerization of application, application cache and MySql scheduled event.

Swagger UI

Every time you develop API it is good advice to document it in some way. Documentation makes API easier to understand and use. 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 make to include Swagger UI in the project.
First, we need to add Maven dependencies to pom.xml file:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

For reference, you can see the complete pom.xml file here.
After adding Maven dependencies it is time to add Swagger configuration.
Inside the config folder, we need to create a new class - SwaggerConfig.java

@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean    
public Docket apiDocket() {   
    return new Docket(DocumentationType.SWAGGER_2)  
        .apiInfo(metadata())    
        .select()    
        .apis(RequestHandlerSelectors.basePackage("com.amarin"))    
        .build();    
}

private ApiInfo metadata(){
    return new ApiInfoBuilder()
    .title("Url shortener API")    
    .description("API reference for developers")    
    .version("1.0")    
    .build();    
    }  
}
Enter fullscreen mode Exit fullscreen mode

At the top of the class, we need to add a couple of annotations.

@Configuration Indicates that a class declares one or more @Beans methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime.

@EnableSwagger2 indicates that Swagger support should be enabled.

Next, we should add Docket bean which provides the primary API configuration with sensible defaults and convenience methods for configuration.

apiInfo() method takes ApiInfo object where we can configure all necessary API information - otherwise, it uses some default values. To make code cleaner we should make a private method that will configure and return ApiInfo object and pass that method as a parameter of apiInfo() method. In this case it is metadata() method.

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 @ApiOperation annotation to add description. Depending on your needs you can use some other annotations.

It is also possible to document DTOs and using @ApiModelProperty which allows you to add allowed values, descriptions, etc.

Caching

According to Wikipedia, 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 in-memory cache which stores cached data in RAM. When data is requested and found in the cache, it is served from RAM instead from the database. This way we avoid calling costly backend when a user requests data.

Url shortener is a type of application that has more read requests than write requests which means it is an ideal application to use cache.

To enable caching in Spring Boot application we just need to add @EnableCaching annotation in UrlShortenerApiApplication class.

After that, in the controller we need to set @Cachable annotation above GET method. This annotation automatically stores the result of the method call to the cache. In @Cachable annotation, we set 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. 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

Dockerization is the process of packaging application and its dependencies in a Docker container. Once we configure Docker container we can easily run the 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 openjdk:13-jdk-alpine   
COPY ./target/url-shortener-api-0.0.1-SNAPSHOT.jar /usr/src/app/url-shortener-api-0.0.1-SNAPSHOT.jar    
EXPOSE 8080    
ENTRYPOINT ["java","-jar","/usr/src/app/url-shortener-api-0.0.1-SNAPSHOT.jar"]
Enter fullscreen mode Exit fullscreen mode

FROM - This is where we set base image for 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 the 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 the application. The command to run an application from the .jar file is

java -jar <app_name>.jar
Enter fullscreen mode Exit fullscreen mode

so we put that 3 words in an array and that is it.

Now when we have Dockerfile we should build the image from it. But like I mentioned before, we first need to create .jar file from our project so COPY command in Dockerfile can work properly. To create executable .jar we are going to use maven. We need to make sure we have maven inside our pom.xml. If maven is missing, we can add it

<build>    
    <plugins>    
        <plugin>    
            <groupId>org.springframework.boot</groupId>    
            <artifactId>spring-boot-maven-plugin</artifactId>    
        </plugin>    
    </plugins>    
</build>
Enter fullscreen mode Exit fullscreen mode

After that, we should just run the command

mvn clean package
Enter fullscreen mode Exit fullscreen mode

After that is done, we can build docker image. We need to make sure we are in the same folder where Dockerfile is so we can run this command

docker build -t url-shortener:latest .
Enter fullscreen mode Exit fullscreen mode

-t is used to tag an image so in our case, that means that the name of the repository will be url-shortener and a 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

docker images  
Enter fullscreen mode Exit fullscreen mode

That will give us something like this

OK now, what? Lets quickly repeat all steps what we did before we do the final step:

  1. Our project is done and we are sure it is building and running without errors
  2. Create new Dockerfile with commands that will create a new container and run out .jar file
  3. Make sure we have maven installed
  4. Pack our project into .jar file using maven
  5. Building docker image from Dockerfile

For the last step, we should build our images. I say images because we will also run a MySql server in the docker container. Database container will be isolated from the application container. To run MySql server in docker container simply run

$ docker run --name shortener -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -p 3306:3306 mysql:8
Enter fullscreen mode Exit fullscreen mode

You can see documentation on Docker hub.

When we have a database running inside a container, we need to configure our application to connect to that MySql server. Inside application.properties set spring.datasource.url to connect to 'shortener' container.

Because we made some changes in our project it is required to repeat steps 4 and 5 from above.

Now when we have docker image we should run our container. We do that with command

docker run -d --name url-shortener-api -p 8080:8080 --link shortener url-shortener
Enter fullscreen mode Exit fullscreen mode

-d means that a Docker container runs in the background of your terminal.
--name lets you set the name of your container
-p : - This is simply mapping ports on your local computer to ports inside container. In this case, we exposed port 8080 inside a 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 the docker image we want to run.

And with this, we are done - in browser visit http://localhost:8080/swagger-ui.html

Now you can publish your image to the Docker hub for example, 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 build and the other is docker-compose.

Multi-stage build

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 build is good for us to avoid manually creating .jar file every time we make some change in our code. With multi-stage build we can define one stage of the build that will do maven package command and the other stage will copy the result from the first build to the filesystem of the docker container.

You can see complete Dockerfile here.

Docker-compose

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 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 MySql container by setting image 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:
docker-compose up

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 event runs every 2 minutes. If our database becomes huge then it could happen that event does not finish execution within its scheduling interval, the result may be multiple instances of the event executing simultaneously.

Conclusion

In this post, I added some details for my Url shortener project.
Swagger UI, docker containers and scheduled events are all useful and popular stuff for developing modern APIs.
In next post you can check out some details regarding deployment, Swagger and cache

Discussion (0)

pic
Editor guide