Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Monitoring Using Spring Boot 2.0, Prometheus, and Grafana (Part 1 — REST API)

DZone's Guide to

Monitoring Using Spring Boot 2.0, Prometheus, and Grafana (Part 1 — REST API)

Read this tutorial in order to learn how to create a REST API for CRUD by using Spring Boot 2.0, Prometheus, and Grafana.

· Integration Zone ·
Free Resource

The State of API Integration 2018: Get Cloud Elements’ report for the most comprehensive breakdown of the API integration industry’s past, present, and future.

In part 1, we will be creating a REST API for CRUD operations using Spring Boot 2.0, JPA, H2 Database, and SWAGGER UI for documentation.

We will be creating a simple application offering CRUD operations over REST for a person entity we shall be using

  • H2: as our underlying database

  • Spring Boot Web: for creating REST API

  • Spring Data JPA: for JPA implementation

  • SWAGGER UI: for documenting API's

So, let's get started by creating a new project.

Create a spring starter project in Eclipse (I am using STS) or you can use Spring Initializer to get started. Add dependencies for Web, Lombok, Actuator, H2, and JPA.Image title


At this point, our project structure should look like this:project structure

Now, let's add controller, entity, model, and service classes.

  • pom .xml: Maven pom file containing all the dependencies

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.satish.monitoring</groupId>
    <artifactId>person-application</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>person-application</name>
    <description>Sample application to be monitored</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <springfox-version>2.5.0</springfox-version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- DB-JPA boot related -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--SpringFox swagger dependencies -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${springfox-version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${springfox-version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-joda</artifactId>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • PersonEntity: JPA entity class representing person table in the database.

package com.satish.monitoring.db.entities;

import java.io.Serializable;

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

import com.satish.monitoring.web.models.Person;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@Table(name = "PERSON")
@NoArgsConstructor
public class PersonEntity implements Serializable{
private static final long serialVersionUID = -8003246612943943723L;

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private int personId;

private String firstName;
private String lastName;
private String email;

public PersonEntity( String firstName, String lastName, String email) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}

public PersonEntity(int personId, String firstName, String lastName, String email) {
super();
this.personId = personId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
}
  • PersonRepository: JPA repository interface.
    package com.satish.monitoring.db.repositories;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;
    
    import com.satish.monitoring.db.entities.PersonEntity;
    
    @Repository
    public interface PersonRepository  extends JpaRepository<PersonEntity, Integer>{
    }
  • PersonService: interface for operations. 
  • package com.satish.monitoring.services;
    
    import java.util.List;
    import java.util.Optional;
    
    import org.springframework.stereotype.Service;
    
    import com.satish.monitoring.web.models.Person;
    
    @Service
    public interface PersonService {
    /**
     * 
     * @param personId
     * @return {@link Optional} {@link Person} objects if present in database
     *         for supplied person ID
     */
    public Optional<Person> getPersonById(int personId);
    
    /**
     * 
     * @return {@link List} of {@link Person} model class fo rall available
     *         entities
     */
    public List<Person> getAllPersons();
    
    /**
     * 
     * @param personId
     * @return Delete the person from database for supplied id
     */
    public boolean removePerson(int personId);
    
    /**
     * 
     * @param person
     * @return {@link Optional} {@link Person} objects after save or update Save
     *         if no personId present else update
     */
    public Optional<Person> saveUpdatePerson(Person person);
    }
    
    • PersonServiceImpl: The implementation class to interact with Database using repository interface.

    package com.satish.monitoring.services;
    
    import java.util.List;
    import java.util.Optional;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import com.satish.monitoring.db.entities.PersonEntity;
    import com.satish.monitoring.db.repositories.PersonRepository;
    import com.satish.monitoring.web.models.Person;
    
    @Component
    public class PersonServiceImpl implements PersonService {
    
    private final PersonRepository personRepo;
    
    @Autowired
    PersonServiceImpl(PersonRepository personRepo) {
    this.personRepo = personRepo;
    }
    
    /**
     * Convert {@link Person} Object to {@link PersonEntity} object Set the
     * personId if present else return object with id null/0
     */
    private final Function<Person, PersonEntity> personToEntity = new Function<Person, PersonEntity>() {
    @Override
    public PersonEntity apply(Person person) {
    if (person.getPersonId() == 0) {
    return new PersonEntity(person.getFirstName(), person.getLastName(), person.getEmail());
    } else {
    return new PersonEntity(person.getPersonId(), person.getFirstName(), person.getLastName(),
    person.getEmail());
    }
    }
    };
    
    /**
     * Convert {@link PersonEntity} to {@link Person} object
     */
    private final Function<PersonEntity, Person> entityToPerson = new Function<PersonEntity, Person>() {
    @Override
    public Person apply(PersonEntity entity) {
    return new Person(entity.getPersonId(), entity.getFirstName(), entity.getLastName(), entity.getEmail());
    }
    };
    
    
    /**
     * If record is present then convert the record else return the empty {@link Optional}
     */
    @Override
    public Optional<Person> getPersonById(int personId) {
    return  personRepo.findById(personId).map(s ->  entityToPerson.apply(s));
    }
    
    @Override
    public List<Person> getAllPersons() {
    return personRepo.findAll().parallelStream()
    .map(s ->  entityToPerson.apply(s))
    .collect(Collectors.toList());
    }
    
    @Override
    public boolean removePerson(int personId) {
    personRepo.deleteById(personId);
    return true;
    }
    
    @Override
    public Optional<Person> saveUpdatePerson(Person person) {
    if(person.getPersonId() == 0 || personRepo.existsById(person.getPersonId())){
    PersonEntity entity = personRepo.save(personToEntity.apply(person));
    return Optional.of(entityToPerson.apply(entity));
    }else{
    return Optional.empty();
    }
    }
    }
  • PersonResource : Controller class to expose endpoints
  • package com.satish.monitoring.web.rest;
    
    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.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.PutMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.satish.monitoring.services.PersonService;
    import com.satish.monitoring.web.models.Person;
    
    
    @RestController
    @RequestMapping("/person")
    public class PersonResource {
    
    private final PersonService personService;
    /**
     * Constructor to autowire PersonService instance.
     *  Look we have declared personService as final without initialization
     */
    @Autowired
    PersonResource(PersonService personService) {
    this.personService = personService;
    }
    
    /**
     * 
     * @return expose GET endpoint to return {@link List} of all available persons
     */
    @GetMapping
    public List<Person> getAllPerson() {
    return personService.getAllPersons();
    }
    
    /**
     * 
     * @param personId supplied as path variable
     * @return expose GET endpoint to return  {@link Person} for the supplied person id 
     * return HTTP 404 in case person is not found in database
     */
    @GetMapping(value = "/{personId}")
    public ResponseEntity<Person> getPerson(@PathVariable("personId") int personId) {
    return personService.getPersonById(personId).map(person -> {
    return ResponseEntity.ok(person);
    }).orElseGet(() -> {
    return new ResponseEntity<Person>(HttpStatus.NOT_FOUND);
    });
    }
    
    /**
     * 
     * @param person JSON body
     * @return  expose POST mapping and return newly created person in case of successful operation
     * return HTTP 417 in case of failure
     */
    @PostMapping
    public ResponseEntity<Person> addNewPerson(@RequestBody Person person) {
    return personService.saveUpdatePerson(person).map(p -> {
    return ResponseEntity.ok(p);
    }).orElseGet(() -> {
    return new ResponseEntity<Person>(HttpStatus.EXPECTATION_FAILED);
    });
    }
    
    /**
     * 
     * @param person JSON body
     * @return  expose PUT mapping and return newly created or updated person in case of successful operation
     * return HTTP 417 in case of failure
     *  
     */
    @PutMapping
    public ResponseEntity<Person> updatePerson(@RequestBody Person person) {
    return personService.saveUpdatePerson(person).map(p -> {
    return ResponseEntity.ok(p);
    }).orElseGet(() -> {
    return new ResponseEntity<Person>(HttpStatus.EXPECTATION_FAILED);
    });
    }
    /**
     * 
     * @param personId person id to be deleted
     * @return expose DELETE mapping and return success message if operation was successful. 
     *  return HTTP 417 in case of failure 
     * 
     */
    @DeleteMapping(value = "/{personId}")
    public ResponseEntity<String> deletePerson(@PathVariable("personId") int personId) {
    if (personService.removePerson(personId)) {
    return ResponseEntity.ok("Person with id : " + personId + " removed");
    } else {
    return new ResponseEntity<String>("Error deleting enitty ", HttpStatus.EXPECTATION_FAILED);
    }
    }
    }
  • SwaggerAPIDocumentationConfig: Configure SWAGGER UI
  • package com.satish.monitoring.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    /**
     *
     * @author Satish Sharma
     *
     */
    @EnableSwagger2
    @Configuration
    public class SwaggerAPIDocumentationConfig {
    
    ApiInfo apiInfo() {
    return new ApiInfoBuilder().title("Person REST CRUD operations API in Spring-Boot 2")
    .description(
    "Sample REST API for monitoring using Spring Boot, Prometheus and Graphana ")
    .termsOfServiceUrl("").version("0.0.1-SNAPSHOT").contact(new Contact("Satish Sharma", "https://github.com/hellosatish/monitoring/person", "https://github.com/hellosatish")).build();
    }
    
    @Bean
    public Docket configureControllerPackageAndConvertors() {
    return new Docket(DocumentationType.SWAGGER_2).select()
    .apis(RequestHandlerSelectors.basePackage("com.satish.monitoring")).build()
     .directModelSubstitute(org.joda.time.LocalDate.class, java.sql.Date.class)
                    .directModelSubstitute(org.joda.time.DateTime.class, java.util.Date.class)
                    .apiInfo(apiInfo());
    }
    }
  • SwagerUIController: To expose SWAGGER UI from context path and redirect all request to Swagger UI.
  • package com.satish.monitoring.web.rest;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class SwaggerUIController {
    @RequestMapping(value = "/")
    public String index() {
    return "redirect:swagger-ui.html";
    }
    }
    • application.properties: Configure properties. Notice we have configured the application to run on port 9000.

    # Server configurations.
    server.port=9000
    logging.level.com.satish.monitoring=debug
    logging.file=logs/monitoring.log
    
    # Database configurations.
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.url=jdbc:h2:file:./db/target/person_db;DB_CLOSE_DELAY=-1
    spring.datasource.username=satish
    spring.datasource.data=classpath:/db-scripts/init-script.sql
    
    spring.h2.console.enabled=true
    spring.h2.console.path=/db-console
    
    spring.jpa.show-sql=true
    # change the below to none in production
    spring.jpa.hibernate.ddl-auto=create-drop
  • Run: We are all set to go. Now, let's run the application using the command below. Or in STS, you can right-click on the project in project explorer and select Run As and then select Spring Boot App .
  • mvn clean spring-boot:run

    Now browse the URL http:localhost:9000 and you should be able to see the SWAGGER UI 

    Image title

    You have successfully created the REST API for CRUD operations. You can have a look/download the code from this GitHub Repo.

    In the next part, we shall be enabling endpoint to expose metrics as JSON.

    Your API is not enough. Learn why (and how) leading SaaS providers are turning their products into platforms with API integration in the ebook, Build Platforms, Not Products from Cloud Elements.

    Topics:
    spring boot 2.0 ,java8 ,rest api ,spring data jpa ,swagger 2 ,prometheus ,grafana ,integration

    Opinions expressed by DZone contributors are their own.

    {{ parent.title || parent.header.title}}

    {{ parent.tldr }}

    {{ parent.urlSource.name }}