Use Docker to Continuously Deploy Spring Boot Applications – Part 1

The full source code of this example can be found on Github.

https://github.com/yang-zhang-syd/spring-boot-docker-ci-example

Spring Boot is a lightweight and fully fledged framework written in Java. It can be used to build RESTful APIs as well as console applications. It provides functionalities such as Dependency Injection, Database Connection Pool Management out of the box so that developers can focus on the business logics instead of struggling with nuts and bolts.

There are a few benefits to do CI for Spring Boot applications in docker containers.

  1. Make the whole CI processes documented in configuration files.
  2. Build and run your application in the exactly same context as your development environment.
  3. Made your application platform independent so that you can deploy your application to either AWS or Azure or an on premise server.
  4. Easier to scale up or scale down your services.
  5. Don’t have to install build tools and libraries on build agent. All dependant tools and frameworks will be installed inside docker container.

We will build three docker images for application development, build and release respectively.

The Dockerfile for develop image is as below:

FROM maven:3.5-jdk-8-alpine

WORKDIR /usr/src/spring-boot-docker-ci-example
COPY pom.xml .
RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve

COPY . .

RUN mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests
ENTRYPOINT [“java”, “-jar”, “/usr/src/spring-boot-docker-ci-example/target/spring-boot-docker-ci-example-0.0.1-SNAPSHOT.jar”]

The build tool of choice is maven and we choose to use alpine as the underlying linux os. In the development dockerfile, we first specify the work directory. Then, we copy the pom.xml and resolve java dependencies. Following step is to copy the source code to work directory and build and package the application. The final step is to tell docker the entry point of our application.

As you may have noticed, the development image has the build tool, all dependent packages as well as the java source code. Also, in this image we have the java jdk installed which is a lot larger than a java jre. With all these overhead, we can use this image as an environment to test and debug our application. IntelliJ has built in support to debug remotely in docker containers. The instructions to set it up can be easily found by googling.

To build the application for release purpose, we will make a build docker image. The dockerfile is as below:

FROM maven:3.5-jdk-8-alpine

WORKDIR /usr/src/spring-boot-docker-ci-example

COPY pom.xml .

RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve

COPY . .

In this build dockerfile, we only have the first 4 steps of the development image dockerfile. We intentionally leave out the step to build and package the application, because we will need to copy the built package to local directory instead of leaving the built files inside the docker container. With the help of docker compose, we can easily achieve this.

The idea is to create a container of the build image and map the built target path to local directory. This way the built results will be made available to us for release purpose. The docker compose file is as below:

version: ‘2’

services:

springboot:

build:

context: .

dockerfile: Dockerfile.build

image: demo/springboot-build

volumes:

# map the output folder to local folder

– ./target:/usr/src/spring-boot-docker-ci-example/target

# The command to package the jar. This will start when the container boots up.

command: /bin/bash -c “mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests”

In above docker compose file, we specified the build context, build dockerfile and image name. The important settings are volume mappings and the command to run after the docker container starts up which are commented above.

Having the release artifacts, the final step is to build the release docker image. The dockerfile to do this is as below:

FROM openjdk:8-jre-alpine

WORKDIR /usr/app

COPY ./target/spring-boot-docker-ci-example-0.0.1-SNAPSHOT.jar .

ENTRYPOINT [“java”, “-jar”, “/usr/app/spring-boot-docker-ci-example-0.0.1-SNAPSHOT.jar”]

In this docker image, we are going to use alpine with java jre. This will give us a much smaller docker container in size compared to the development one. We simply copy the release artifacts and specify the entry point to our java application.

With the release docker image, we are ready to deploy it to the production server. In another article, I will write about how to automate the process in Jenkins.

《做你自己:彼得巴菲特自传》读后感

彼得巴菲特是著名投资人沃伦巴菲特的儿子。沃伦对彼得人生的影响并不像外人所想像的用金钱在经济上进行资助,而更多的是在价值观、世界观的养成。而彼得自己也丝毫没有把他生于富人家的幸运作为自己的资本,更没有凌驾于他人之上的优越感。彼得更多的是在探寻个人的志向,生命的意义,人生的抉择,成功的定义以及对社会的回馈。

这里我要写下从我的角度读完他的书之后的感想和收获。

太多的人把赚钱作为自己的志向。现实生活中我们听说过美国梦、澳洲梦,现在也出现了中国梦。这些梦想无非把房子、汽车、度假旅游作为一个人成功的标志。哪怕一个人实现了有房、有车,那么他下一步很可能会以拥有更多的房子,更名贵的汽车,银行里更多的存款为目标。这些梦想与其说是为普通人设定的目标不如说是套牢他们的绳索。这样的梦想鼓励人们去买超出自己支付能力的住房,消费远超出实用价值的豪华汽车,度假旅游也变成了模板化的商业活动。于是人们大多陷入了为了工资收入而工作,为了保住工作出卖了自己的时间和自由。进而人们迷失了自己,少有时间思考自己的志向和生活的意义。

我喜欢这样一个比喻:金钱就像赛场上的积分,而人生就像比赛,我们在赛场上不会时刻紧盯积分牌,在人生中我们也不该紧盯着金钱。我们大多看到了金钱是用来购买商品、资产、服务的媒介。他们以为金钱就是实现自己理想,标志自己成功的不二选择。而彼得巴菲特认为金钱不过是热衷于自己工作而取得的副产品。而一个人要想出色的工作,必然要从事符合其志向的事情。如果你对自己的工作没有激情,或者工作内容不是你的特长。你对工作的选择仅仅是出于父母、家庭的期望,或者更糟糕的紧紧是因为某项工作更容易赚钱,那么工作对你来说必然是痛苦的、折磨的,你无时无刻不是想着逃离这个糟糕的选择。如果是这样很难想象你能够全情投入到你的事业中去,你自身的价值的发挥也更无从谈起。所以,重要的不是金钱而是找到自己的志向,找到自己能力、兴趣、特长的交汇。用良好的心态全情投入到自己热衷的有意义的事情上。生活种我们要避免被金钱绑架,学会过简谱的生活。过分追求物质享受那么难免要把时间浪费在赚取蝇头小利的事情上。我们要意识到自己唯一重要的财富不是金钱而是时间。为了节省金钱而白白浪费自己的时间是最得不偿失的事情。

我们要放弃抱怨生活的不公与不平等。我们生于何处、何时不受我们意愿的左右。每一个人其人生起点必然不同。抱怨生活的不公是一个危险的陷阱。它让我们把所有失败、所有挫折都可以归结为这个原因,而不去设法自己掌控命运。人生重要的不是起点,而是终点到达何处。

我们永远有机会也有权利去选择。选择是一个不断尝试不断修正的迭代过程,也是一个不断尝试、犯错、汲取教训的过程。与其终日所思,不如须臾之行。大胆的去尝试,勇敢的去失败。事业的失败、愿望的落空并不意味着终结。只要我们不断的从失败中汲取教训,下一次尝试的时候就有一个更好的自己。失败和挫折是一个成功的人所必须要有的人生精力。做事要超过一般水平一定不会一帆风顺,一定会遇到挫折,一定是一个痛苦煎熬的过程。人生拼的是耐力,是长跑,我们不可能一蹴而就。

很多时候我们在工作中,生活中太多关注自己应得的东西。实际上我们所得到的永远都是自己赢得的。我们大多数时候都是在被低估,而想要获得承认只能够通过努力去赢得。我们要通过自己一点一滴的成就去赢得别人的信任和尊重。哗众取众、阿谀逢迎在人生的长跑种的作用可以忽略不计。

书中提到了一个非常有用的想法就是多维度知识的打造也就是跨界。一个人要想在一个专一的领域发展到前百分之五是非常艰难的。但是如果你能够做到在相关的多个领域都做到前百分之二十五那么你的综合实力将一定是人群中前百分之一的。

奋斗的过程中我们要持开放的态度与人合作,积极思考、虚心采纳他人的意见。创造性是在与他人的交流合作中产生的。固执己见没有任何益处。我喜欢书中磨刀的比喻。工作生活中与人合作沟通就像是磨刀,我们需要摆正自己的位置,积极的迎接生活工作中的磨练。

对于自己的作品、产品、成果,不要固执己见。只有能够被大众接受的,能够对他人、社会有用的才是有价值的。如果我们一直得到消极的反馈,应该先停下来思考一下自己的方向是否正确。我们要时刻考虑、关注自身与周围环境的互动,并以积极的心态去面对各种各样的意见和反馈。
站在作者的角度他是最有资格讨论关于巨额财富对人的影响。财富对于满足基本的生活需求来说固然重要。但是有钱人的人生与普通人的人生一样面对着同样的问题和挑战。任何人都需要找到自己生命的目标和意义,任何人都需要通过磨练来找到自信和自我认知。财富对于迷失自我价值的人来说也许是个诅咒。读完这本书我开始理解美国的这些富人为什么能够将自己的财富捐赠出来回馈社会。他们留给自己后代的是更宝贵的人生哲理,而财富不过是过好这一生的副产品。

Writing SOLID Code to Enable Continuous Delivery & Integration

Why do we need Continuous Delivery & Continuous Integration?

In today’s highly competitive IT industry no one can afford to spend years developing a piece of software. There are always smart people with same or similar ideas and users can always find alternatives if they are unhappy with what you can provide. In this sense, it is always important to get to the market faster.

Also, as it says in the book ‘The Lean Startup’, it is important to get user feedback at an early stage. Not only to justify your ideas but also to evolve your ideas. The feedback channel is an indispensable part of your iteration loop. We cannot rely on our own imagination about what the users want and what the market needs but to evolve our ideas in an interactive way with the real end users.

So, it is all about speed! This process is actually a loop:

release  ——> feedback ——> improve ——> next release

Developing software, Continuous Delivery & Continuous Integration are essentially about ITERATIONS. It is like wheels. The faster you spin your wheels the more likely you get the advantages against your competitors and with improved user satisfaction.

Problems of CD/CI in real life

While the intentions for CD/CI are good, in practice, quite often it does not provide as many benefits as it should.

Working in the industry, I find many times people set it up as an overhead. It becomes a part of the corporate process programmers are required to follow. If this is the case, then it will provide no benefit but to slow down the development.

CD/CI is not a magic but a process to assist developers. To make it work, it essentially comes down to developers’ programming skills, the code quality and the architecture of the software.

SOLID Principles The Most Fundamental Software Principles

SOLID principles are around for many years. Unfortunately, this is still a skill hard to get from current education systems. Not until those graduates start working in the industry and collaborate with others on significant projects. A few will realize the benefits of these principles, while some others, who are not keen to improve will just keep repeatedly doing basic coding.

People always describe programmer is a discipline only for young people. I would say this is the case for the fall behinders who are satisfied with a coding position for the salary but not interested in paying attention to the big picture.

The SOLID principles are the fundamental principles one level above all other design patterns. It enables other techniques, like TDD/BDD. I am not intended to explain it in this article. You can find good examples by simply google the term. I cannot emphasize more on how important those principles are to programming and I cannot imagine any people working in the software industry can ignore it.

If your code is not SOLID, I believe it is 100% that no matter how well defined your CD/CI process is set up you will essentially get no benefits from it. SOLID are essentially the characteristics a maintainable and evolvable piece of software should have.

If you feel it is still too much for the SOLID principles, I would recommend following the following the simple rule below to start with:

Program to interfaces and depend on abstractions.

To wrap it up, engineering practices are to speed up your iterations. There is no magic to get the benefits out of the box straight way. So if you find the CD/CI slows the team down, the problem may lie in developers’ coding skills.

D3 Projection on Google Maps

Summary

This blog explains how to use d3 to draw shapes on google maps and enable users to interact with them.

Full Code on Plunker

Problem

Use d3 to draw shapes, polygons, lines on the map at specified geo locations. Also, the d3 components need to be interactive which means the DOM events need to be captured and processed.

Usually, when using d3, SVG is used as the parent container for all d3 shapes. When drawing on the map all shapes are bound to geo-locations, which means the SVG need to grow and shrink as we zoom in or zoom out on the map and move the SVG with the map when we drag and drop. This introduces the challenge of correctly project geo-locations to d3 coordinates.

Solution

Google Maps provides OverLayView to add custom layers to the map. We are going to draw our d3 shapes on one of those exposed google map layers.

According to Google Maps MapPanes document, google maps exposed a few layer to customize. In our case, we need the d3 shapes to capture DOM events so we need to use overlayMouseTarget layer.

This blog will demonstrate drawing zones on google map and the code will be available in this Plunker.

First, we add a custom OverLayView to google map.

var overlay = new google.maps.OverlayView();
overlay.onAdd = function() {}
overlay.draw = function() {}
overlay.setMap(map);

Second, we define the projection help method.

function googleProjection(prj) {
return function(lnglat) {
ret = prj.fromLatLngToDivPixel(new google.maps.LatLng(lnglat[1],lnglat[0]))
return [ret.x, ret.y]
};
}

The projection help method creates another function which is the actual projector method used to translate longitude and latitude pair to map coordinates. The prj parameter will be a utility object provided by google maps OverlayView.

Third, create an SVG element.

var projection = googleProjection(overlay.getProjection());
var northWest = projection([139.499822,-27.183773]);
var southEast = projection([155.642935,-38.559921]);
var width = southEast[0] – northWest[0];
var height = southEast[1] – northWest[1];
svg = d3.select(overlay.getPanes().overlayMouseTarget)
.append(“svg”)
.style(“position”, “absolute”)
.style(“top”, northWest[1])
.style(“left”, northWest[0])
.attr(“height”, height)
.attr(“width”, width);

To make d3 shapes interactive, we add the SVG layer on overlayMouseTarget layer defined by google map.

This is an important step because the SVG needs to be big enough to cover all the geo locations our data use. The code above we pass the latlng pairs which covering the entire NSW Australia to the projection method to get the North West and South East coordinates on map. Then, we calculate the width and height of the SVG. On creation of SVG, we pass the north-west coordinates as the top left coordinates on the page.

Based on your data coverage, you can choose the latlng pairs accordingly.

Fourth, get GeoJson data and draw shapes on the map.

svg.selectAll(“path”)
.data(data.features)
.enter().append(“path”)
.attr(‘transform’, ‘translate(‘+(-northWest[0])+’ ‘+(-northWest[1])+’)’)
.attr(“d”, path)
.attr(“fill”, “#666666”)
.attr(“fill-opacity”, 0.3)
.attr(“stroke”, “black”);

Because we have set the top left of our SVG to the North East coordinates on map, when drawing shapes we need to translate the coordinates to reflect this. The following line of code does exactly what we want to achieve.

      .attr(‘transform’, ‘translate(‘+(-northWest[0])+’ ‘+(-northWest[1])+’)’)

Fifth, make shapes responsive to DOM events.

We will show a tool tip on mouse over the d3 shapes. Following is the tooltip element we create. Initially, it is hidden by setting style opacity to 0.

var tipSvg = d3.select(‘body’).append(‘div’)
.style(‘position’, ‘absolute’)
.style(‘max-width’, ‘400px’)
.style(‘height’, ‘auto’)
.style(‘background-color’, ‘#ffffff’)
.style(‘opacity’, 0)
.style(‘width’, 600);

To make the shapes on map responsive we need to update the code drawing shapes as below.

svg.selectAll(“path”)
.data(data.features)
.enter().append(“path”)
.attr(‘transform’, ‘translate(‘+(-northWest[0])+’ ‘+(-northWest[1])+’)’)
.attr(“d”, path)
.attr(“fill”, “#666666”)
.attr(“fill-opacity”, 0.3)
.attr(“stroke”, “black”)
.on(“mouseover”, mapMouseOver)
.on(“mouseout”, mapMouseOut);

function mapMouseOver(d) {
var tip = ‘<p>’ + d.properties.nsw_loca_2 + ‘</p>’;
tipSvg.html(tip)
.style(‘left’, (d3.event.pageX) + ‘px’)
.style(‘top’, (d3.event.pageY) + ‘px’);
tipSvg.transition()
.duration(500)
.style(‘opacity’, 1);
}

function mapMouseOut(d) {
tipSvg.transition()
.duration(500)
.style(‘opacity’, 0);
}

The result is that on mouse over the d3 shape the tooltip shows up with the name of the suburb. Then, the tooltip will be hidden again on mouse out.

The full code can be found here:

Link to Plunker

Reference

https://www.safaribooksonline.com/blog/2014/02/11/d3-js-maps/