Category Archives: Spring

Spring Boot Kubernetes

Spring Boot Kubernetes

This guide walks you through the process of deploying a Spring Boot application on Kubernetes. There are many choices of how to do things with Spring Boot and Kubernetes — the intention with this guide is to get you up and running as quickly as possible, not to discuss all the alternatives or go into all the details of how you get to production (which is, of course, our favourite place to be).

There are some interactive tutorials that complement and extend the content of this guide on Katacoda/springguides. If you follow those tutorials, all the code will be running in the cloud from your browser. Or you can create your own cluster and install all the tools you need locally, then copy paste from the guides.Getting Started with Spring Boot on Kubernetes: the same material as this guide, but running in your browser.Install Kubernetes: a guide to installing Kubernetes locally using Kind. You can use this to get setup on your laptop if you prefer to run the tutorials there.Kubernetes Probes with Spring Boot: a guide to liveness and readiness probes with Spring Boot.

What you’ll build

Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications. It groups containers that make up an application into logical units for easy management and discovery. In this guide we will build and deploy a simple Spring boot application.

There is also a Getting Started Guide and a Topical Guide on Docker, which cover some of the background on building a container image.

What you’ll need

You will need a Linux or Linux-like command line. Command line examples in this guide work on Linux, a MacOS terminal with a shell, or WSL on Windows.

You will also need a Kubernetes cluster and the command line tool Kubectl. You can create a cluster locally using Kind (on Docker) or Minikube. Or you can use a cloud provider, such as Google Cloud PlatformAmazon Web Services or Microsoft Azure. Before proceeding further, verify you can run kubectl commands from the shell. E.g. (using kind):

$ kubectl cluster-info
Kubernetes master is running at https://127.0.0.1:46253
KubeDNS is running at https://127.0.0.1:46253/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

and

$ kubectl get all
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   7m13s

Create a Spring Boot Application

The first thing we will do is create a Spring Boot application. If you have one you prefer to use already in github, you could clone it in the terminal (git and java are installed already). Or you can create an application from scratch using start.spring.io:

curl https://start.spring.io/starter.tgz -d dependencies=webflux,actuator | tar -xzvf -

You can then build the application:

./mvnw install
It will take a couple of minutes the first time, but then once the dependencies are all cached it will be fast.

And you can see the result of the build. If the build was successful, you should see a JAR file, something like this:

ls -l target/*.jar
-rw-r--r-- 1 root root 19463334 Nov 15 11:54 target/demo-0.0.1-SNAPSHOT.jar

The JAR is executable:

$ java -jar target/*.jar

The app has some built in HTTP endpoints by virtue of the “actuator” dependency we added when we downloaded the project. So you will see something like this in the logs on startup:

...
2019-11-15 12:12:35.333  INFO 13912 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2019-11-15 12:12:36.448  INFO 13912 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
...

So you can curl the endpoints in another terminal:

$ curl localhost:8080/actuator | jq .
{
  "_links": {
    "self": {
      "href": "http://localhost:8080/actuator",
      "templated": false
    },
    "health-path": {
      "href": "http://localhost:8080/actuator/health/{*path}",
      "templated": true
    },
    "health": {
      "href": "http://localhost:8080/actuator/health",
      "templated": false
    },
    "info": {
      "href": "http://localhost:8080/actuator/info",
      "templated": false
    }
  }
}

To complete this step, send Ctrl+C to kill the application.

Containerize the Application

There are multiple options for containerizing a Spring Boot application. For local development and testing it makes sense to start with a Dockerfile as the docker build workflow is generally well known and understood.

If you don’t have docker locally or want to automatically push an image to a registry then Jib would be a good choice. In an enterprise setting, when you need a trusted build service for a CI/CD pipeline, you could look at Cloud Native Buildpacks.

First create a Dockerfile:

FROM openjdk:8-jdk-alpine AS builder
WORKDIR target/dependency
ARG APPJAR=target/*.jar
COPY ${APPJAR} app.jar
RUN jar -xf ./app.jar

FROM openjdk:8-jre-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY --from=builder ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=builder ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=builder ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.demo.DemoApplication"]

Then build the container image, giving it a tag (choose your own ID instead of “springguides” if you are going to push to Dockerhub):

$ docker build -t springguides/demo .

You can run the container locally:

$ docker run -p 8080:8080 springguides/demo

and check that it works in another terminal:

$ curl localhost:8080/actuator/health

Finish off by killing the container.

You won’t be able to push the image unless you authenticate with Dockerhub (docker login), but there’s an image there already that should work. If you were authenticated you could:

$ docker push springguides/demo

In real life the image needs to be pushed to Dockerhub (or some other accessible repository) because Kubernetes pulls the image from inside its Kubelets (nodes), which are not in general connected to the local docker daemon. For the purposes of this scenario you can omit the push and just use the image that is already there.

Just for testing, there are workarounds that make docker push work with an insecure local registry, for instance, but that is out of scope for this scenario.

Deploy the Application to Kubernetes

You have a container that runs and exposes port 8080, so all you need to make Kubernetes run it is some YAML. To avoid having to look at or edit YAML, for now, you can ask kubectl to generate it for you. The only thing that might vary here is the --image name. If you deployed your container to your own repository, use its tag instead of this one:

$ kubectl create deployment demo --image=springguides/demo --dry-run -o=yaml > deployment.yaml
$ echo --- >> deployment.yaml
$ kubectl create service clusterip demo --tcp=8080:8080 --dry-run -o=yaml >> deployment.yaml

You can take the YAML generated above and edit it if you like, or you can just apply it:

$ kubectl apply -f deployment.yaml
deployment.apps/demo created
service/demo created

Check that the application is running:

$ kubectl get all
NAME                             READY     STATUS      RESTARTS   AGE
pod/demo-658b7f4997-qfw9l        1/1       Running     0          146m

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.43.0.1       <none>        443/TCP    2d18h
service/demo         ClusterIP   10.43.138.213   <none>        8080/TCP   21h

NAME                   READY     UP-TO-DATE   AVAILABLE   AGE
deployment.apps/demo   1/1       1            1           21h

NAME                              DESIRED   CURRENT   READY     AGE
replicaset.apps/demo-658b7f4997   1         1         1         21h
d
Keep doing kubectl get all until the demo pod shows its status as “Running”.

Now you need to be able to connect to the application, which you have exposed as a Service in Kubernetes. One way to do that, which works great at development time, is to create an SSH tunnel:

$ kubectl port-forward svc/demo 8080:8080

then you can verify that the app is running in another terminal:

$ curl localhost:8080/actuator/health
{"status":"UP"}

Source: https://spring.io/guides/gs/spring-boot-kubernetes/

Interagindo SpringData com MongoDB

Introdução

Este post apresenta de forma simples, como interarir com MongoDB, usando SpringData.

Pré-requisitos

Para efetuar os testes, é necessario estar com o MongoDB rodando. Veja como instalar e executar o MongoDB aqui.

Interface MongoRepository

O MongoRepository é implementado com muitas APIs para armazenar e recuperar dados. Podemos usar o MongoRepository com operações CRUD genéricas completas da interface CrudRepository, e métodos adicionais da interface PagingAndSortingRepository para paginação e ordenação. O MongoRepository também estende o QueryByExampleExecutor para permitir a execução da consulta por exemplo.

Criar projeto SpringBoot

Criar o projeto usando Spring Initializr ou STS (SpringSource Tool Suite)

Arquivo Maven e Dependências

O arquivo de configuração (pom.xml) deve ter a configuração do SpringBoot e dependência do spring-boot-starter-data-mongodb.

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>1.5.7.RELEASE</version>
</parent>

<groupId>br.com.whs.springmongodb</groupId>
<artifactId>spring-mongodb</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>spring-mongodb</name>

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-mongodb</artifactId>
   </dependency>
</dependencies>

Classes do Projeto

Classe de Entidade

São as classes responsáveis pelo mapeamento da persistência no banco de dados.

Utilizam 2 anotações:

  • @Document – identifica o objeto de domínio da persistência no MongoDB
  • @Id – determina o Identificador do documento (neste caso da collection)

Classe Repository

As classes deve estender (extends) a classe MongoRepository.

Classe Service

Possuem função de fachada entre as classes Web e o Repository.

Classe Controller

Neste caso, utilizamos essas classes para atender as chamadas REST.

Classe Application

Esta é a classe responsável pelo funcionamento do SpringBoot. Como os valores de configuração foram mantidos, ele rodará sob o Tomcat (embedded) na porta 8080.

Rodando a aplicação

Pode rodar a aplicação de 2 formas:

  • Rodando a classe SpringMongoDBApplication (Run / Run As / Java Application)
  • Executando o comando $ mvn tomcat:run

Postman

Para rodar os testes, utilizamos o Postman, uma poderosa ferramenta para testar endpoints e APIs.

Adicionar User

Configurar o Postman, da seguinte forma:

  • Method: POST
  • URL: http://localhost:8080/user
  • Body: raw
  • Tipo: JSON (application/json)
  • Exemplo:
{
  "name": "lfchaim",
  "profile": [
    {
      "name": "admin"
    },
    {
      "name": "dev"
    }
  ],
  "age": 36,
  "email": "lfchaim@gmail.com"
}

Listar User

A listagem, sem parâmetros, retornará todos os registros do banco (collection: user).

Configuração:

  • Method: GET
  • URL: http://localhost:8080/user

O projeto completo pode ser baixado no Github.

Desenvolvendo uma aplicação Spring Data REST e H2

Desenvolvendo uma aplicação Spring Data, REST e H2

Criando a aplicação

Acesse o site http://start.spring.io/ e configure uma aplicação conforme imagem abaixo.

Baixe e descompacte o arquivo (spring-rest-data-h2.zip) em algum diretório de trabalho.

Abra o Eclipse e importe o projeto (Maven Project).

Criação da classe controller

No Eclipse, clique em File / New / Java Class e defina-a conforme abaixo.

Código

package br.com.whs.springrestdatah2.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import br.com.whs.springrestdatah2.Model.User;
import br.com.whs.springrestdatah2.repository.UserRepository;

@RestController
@RequestMapping("/user")
public class UserController {
        @Autowired
        private UserRepository userRepo;
    
        @RequestMapping(value = "/list", method = RequestMethod.GET)
        public List<User> findAll(){
                return userRepo.findAll();
        }

        @RequestMapping(value = "/find-by-id/{id}", method = RequestMethod.GET)
        public ResponseEntity<?> findById(@PathVariable final Long id){
                User user = userRepo.findOne(id);
                if( user == null ){
                        String msg = "{\"message\":\"User with id " + id + " not found.\"}";
                        return new ResponseEntity<String>(msg,HttpStatus.NOT_FOUND);
                }else{
                        return new ResponseEntity<User>(user,HttpStatus.OK);
                }
        }

        @RequestMapping(value = "/find-by-login/{loginName}", method = RequestMethod.GET)
        public ResponseEntity<?> findByLoginName(@PathVariable final String loginName){
                User user = userRepo.findByLoginName(loginName);
                if( user == null ){
                        String msg = "{\"message\":\"User with login " + loginName + " not found.\"}";
                        return new ResponseEntity<String>(msg,HttpStatus.NOT_FOUND);
                }else{
                        return new ResponseEntity<User>(user,HttpStatus.OK);
                }
        }
        @RequestMapping(value = "/create", method = RequestMethod.POST)
        public ResponseEntity<User> create(@RequestBody final User user){
                userRepo.save(user);
                return new ResponseEntity<User>(userRepo.findByLoginName(user.getLoginName()),HttpStatus.OK);
        }

        @RequestMapping(value = "/update/{id}", method = RequestMethod.PUT)
        public ResponseEntity<?> update(@PathVariable("id") long id, @RequestBody User user) {
            User userData = userRepo.findOne(id);
            if (userData == null) {
                String msg = "{\"message\":\"User with id " + id + " not found.\"}";
                return new ResponseEntity<String>(msg,HttpStatus.NOT_FOUND);
            }
            userData.setLoginName(user.getLoginName());
            userData.setFullName(user.getFullName());
            userData.setPassword(user.getPassword());
            userRepo.save(userData);
            return new ResponseEntity<User>(userData, HttpStatus.OK);
        }
    
        @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
        public ResponseEntity<?> delete(@PathVariable("id") long id) {
            User user = userRepo.findOne(id);
            if (user == null) {
                String msg = "{\"message\":\"User with id " + id + " not found.\"}";
                return new ResponseEntity<String>(msg,HttpStatus.NOT_FOUND);
            }
            userRepo.delete(id);
            return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
        }
}

Criando a interface UserRepository

Código

package br.com.whs.springrestdatah2.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;

import br.com.whs.springrestdatah2.Model.User;

@Component
public interface UserRepository extends JpaRepository<User, Long>{
    public User findByLoginName(String loginName);
}

 

Criando a classe Model (User.java)

Código

package br.com.whs.springrestdatah2.Model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;
    private String loginName;
    private String fullName;
    private String password;
public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public String getLoginName() {
    return loginName;
}

public void setLoginName(String loginName) {
    this.loginName = loginName;
}

public String getFullName() {
    return fullName;
}

public void setFullName(String fullName) {
    this.fullName = fullName;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

}

 

Rodando a aplicação

Essa é uma aplicação Spring Boot, onde o framework baixará e utilizará os recursos necessários, conforme a configuração (definida na Criação da Aplicação). Neste caso, será utilizado Tomcat e banco de dados H2 (ambos embedded)

Procedimento

  1. Abra a classe SpringRestDataH2Application.java
  2. Clique no menu Run / Run as / Java Aplication
  3. Espere até que apareça “Tomcat started on port(s): 8080 (http)”
  4. Abra um navegador e forneça a url de listagem (http://localhost:8080/user/list)
  5. Deverá aparecer apenas 2 colchetes ([]), pois o List está vazio

 

Postman

Para facilitar as chamadas de POST (/user/create), PUT (/user/update) e DELETE (/user/delete), vou utilizar o Postman.

Método POST

Método PUT

Método DELETE

Código-Fonte (github)

https://github.com/lfchaim/spring-rest-data-h2