JavaRush /Java Blog /Random EN /REST API and another test task.
Денис
Level 37
Киев

REST API and another test task.

Published in the Random EN group
Part I: Beginning Where to start? Oddly enough, but from the technical specifications. It is extremely important to make sure that after reading the submitted TOR you fully understand what is written in it and what the client expects. Firstly, this is important for further implementation, and secondly, if you do not implement what is expected of you, it will not be to your advantage. To avoid wasting air, let’s sketch out a simple technical specification. So, I want a service to which I can send data, it will be stored on the service and returned to me at will. I also need to be able to update and delete this data if necessary . A couple of sentences doesn't seem like a clear thing, right? How do I want to send data there? What technologies to use? What format will this data be in? There are also no examples of incoming and outgoing data. Conclusion - the technical specification is already bad . Let's try to rephrase: We need a service that can process HTTP requests and work with transferred data. This will be the personnel records database. We will have employees, they are divided by departments and specialties, employees may have tasks assigned to them. Our task is to automate the process of accounting for hired, fired, transferred employees, as well as the process of assigning and canceling tasks using the REST API. As Phase 1, we are currently working only with employees. The service must have several endpoints to work with it: - POST /employee - POST request, which must accept a JSON object with data about the employee. This object must be saved to the database; if such an object already exists in the database, the information in the fields must be updated with new data. - GET /employee - GET request that returns the entire list of employees saved in the database - DELETE - DELETE /employee to delete a specific employee Employee data model:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
Part II: Tools for the job So, the scope of work is more or less clear, but how are we going to do it? Obviously, such tasks in the test are given with a couple of application goals, to see how you code, to force you to use Spring and to work a little with the database. Well, let's do this. We need a SpringBoot project with REST API support and a database. On the website https://start.spring.io/ you can find everything you need. REST API or another test task.  - 1 You can select the build system, language, SpringBoot version, set artifact settings, Java version, and dependencies. Clicking the Add Dependencies button will bring up a characteristic menu with a search bar. The first candidates for the words rest and data are Spring Web and Spring Data - we will add them. Lombok is a convenient library that allows you to use annotations to get rid of kilometers of code with getter and setter methods. By clicking the Generate button we will receive an archive with the project which can already be unpacked and opened in our favorite IDE. By default, we will receive an empty project, with a configuration file for the build system (in my case it will be gradle, but with Maven things are no fundamental difference, and one spring startup file) Attentive people could pay attention REST API or another test task.  - 2 to two things. First, I have two settings files application.properties and application.yml. By default, you will get exactly properties - an empty file in which you can store settings, but to me the yml format looks a little more readable, now I will show a comparison: REST API or another test task.  - 3 Despite the fact that the picture on the left looks more compact, it is easy to see a large amount of duplication in the properties path. The picture on the right is a regular yml file with a tree structure that is quite easy to read. I will use this file later in the project. The second thing that attentive people might notice is that my project already has several packages. There is no sane code there yet, but it’s worth going through them. How is an application written? Having a specific task, we must decompose it - break it down into small subtasks and begin their consistent implementation. What is required of us? We need to provide an API that the client can use; the contents of the controller package will be responsible for this part of the functionality. The second part of the application is the database - the persistence package. In it we will store such things as Database Entities (Entities) as well as Repositories - special spring interfaces that allow you to interact with the database. The service package will contain service classes. We will talk about what the Spring type Service is below. And last but not least, the utils package. Utilitarian classes with all sorts of auxiliary methods will be stored there, for example, classes for working with date and time, or classes for working with strings, and who knows what else. Let's start implementing the first part of the functionality. Part III: Controller
@RestController
@RequestMapping("${application.endpoint.root}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping("${application.endpoint.employee}")
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }
}
Now our EmployeeController class looks like this. There are a number of important things worth paying attention to here. 1. Annotations above the class, the first @RestController tells our application that this class will be an endpoint. 2. @RequestMapping, although not mandatory, is a useful annotation; it allows you to set a specific path for the endpoint. Those. in order to knock on it, you will need to send requests not to localhost:port/employee, but in this case to localhost:8086/api/v1/employee Actually, where did these api/v1 and employee come from? From our application.yml If you look closely, you can find the following lines there:
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
As you can see, we have such variables as application.endpoint.root and application.endpoint.employee, these are exactly what I wrote in the annotations, I recommend remembering this method - it will save a lot of time on expanding or rewriting the functionality - it is always more convenient to have everything in the config , and not hardcode the entire project. 3. @RequiredArgsConstructor is a Lombok annotation, a convenient library that allows you to avoid writing unnecessary things. In this case, the annotation is equivalent to the fact that the class will have a public constructor with all fields marked as final
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
But why should we write such a thing if one annotation is enough? :) By the way, congratulations, this most private final field is nothing more than the notorious Dependency Injection. Let's move on, actually, what kind of field is employeeService? This will be one of the services in our project that will process requests for this endpoint. The idea here is very simple. Each class should have its own task and should not be overloaded with unnecessary actions. If this is a controller, let it take care of receiving requests and sending responses, but we’d rather entrust the processing to an additional service. The last thing left in this class is the only method that returns a list of all employees of our company using the above-mentioned service. The list itself is wrapped in an entity called ResponseEntity. I do this so that in the future, if necessary, I can easily return the response code and message I need, which the automated system can understand. So, for example, ResponseEntity.ok() will return the 200th code, which will say that everything is fine, but if I return, for example
return ResponseEntity.badRequest().body(Collections.emptyList());
then the client will receive code 400 - bad reuqest and an empty list in the response. Typically this code is returned if the request is incorrect. But one controller will not be enough for us to start the application. Our dependencies will not allow us to do this, because we still must have a base :) Well, let's move on to the next part. Part IV: simple persistence Since our main task is to launch the application, we will limit ourselves to a couple of stubs for now. You have already seen in the Controller class that we return a list of objects of type Employee, this will be our entity for the database. Let's create it in the demo.persistence.entity package . In the future, the entity package can be supplemented with other entities from the database.
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
This is a class as simple as a door, the Annotations of which say exactly the following: this is a database entity @Entity, this is a class with data @Data - Lombok. The helpful Lombok will create for us all the necessary getters, setters, constructors - complete stuffing. Well, a little cherry on the cake is @Accessors(chain = true) In fact, this is a hidden implementation of the Builder pattern. Suppose you have a class with a bunch of fields that you want to assign not through the constructor, but by methods. In different order, perhaps not all at the same time. You never know what kind of logic will be in your application. This annotation is your key to this task. Let's look:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Let's assume that we have all these fields in our class😄You can assign them, you can not assign them, you can mix them in places. In the case of only 3 properties, this does not seem like something outstanding. But there are classes with a much larger number of properties, for example 50. And write something like
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
Doesn't look very pretty, does it? We also need to strictly follow the order of adding variables in accordance with the constructor. However, I digress, let's get back to the point. Now we have one (mandatory) field in it - a unique identifier. In this case, this is a Long type number, which is generated automatically when saved to the database. Accordingly, the @Id annotation clearly indicates to us that this is a unique identifier; @GeneratedValue is responsible for its unique generation. It is worth noting that @Id can be added to fields that are not automatically generated, but then the issue of uniqueness will need to be dealt with manually. What could be a unique employee identifier? Well, for example, full name + department... however, a person has full namesakes, and there is a chance that they will work in the same department, small, but there is - that means the decision is bad. It would be possible to add a bunch of other fields, such as date of hire, city, but all this, it seems to me, complicates the logic too much. You may wonder, how is it even possible for a bunch of fields to be unique at once? I answer - maybe. If you're curious, you can google about such a thing as @Embeddable and @Embedded Well, we're done with the essence. Now we need a simple repository. It will look like this:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Yes, that's all. Just an interface, we called it EmployeeRepository, it extends JpaRepository which has two typed parameters, the first is responsible for the data type it works with, the second for the key type. In our case, these are Employee and Long. That's enough for now. The final touch before launching the application will be our service:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
There is the already familiar RequiredArgsConstructor and the new @Service annotation - this is what usually denotes the business logic layer. When running a spring context, classes marked with this annotation will be created as Beans. When in the EmployeeController class we have created the final property EmployeeService and attached RequiredArgsConstructor (or created a constructor by hand) Spring, when initializing the application, it will find this place and slip us a class object into this variable. The default here is Singleton - i.e. there will be one object for all such links; this is important to take into account when designing the application. Actually, that’s all, the application can be launched. Don't forget to enter the necessary settings in the config. REST API or another test task.  - 4 I will not describe how to install a database, create a user and the database itself, but I will just note that in the URL I use two additional parameters - useUnicore=true and characterEncoding=UTF-8. This was done so that the text would be displayed more or less equally on any system. However, if you are too lazy to tinker with the database and really want to poke around the working code, there is a quick solution: 1. Add the following dependency to build.gradle:
implementation 'com.h2database:h2:2.1.214'
2. In application.yml you need to edit several properties, I will give a complete example of the spring section for simplicity:
spring:
  application:
    name: "employee-management-service"
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:./mydb
    username: sa
    password:
The database will be stored in the project folder, in a file called mydb . But I would recommend installing a full-fledged database 😉 Useful article on the topic: Spring Boot With H2 Database Just in case, I’ll provide a full version of my build.gradle to eliminate discrepancies in dependencies:
plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'mysql:mysql-connector-java:8.0.30'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
The system is ready to launch: REST API or another test task.  - 5 You can check it by sending a GET request from any suitable program to our endpoint. In this particular case, a regular browser will do, but in the future we will need Postman. REST API or another test task.  - 6 Yes, in fact, we have not yet implemented any of the business requirements, but we already have an application that starts and can be expanded to the required functionality. Continued: REST API and Data Validation
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION