Create Server Components authored by Joel Gingrich's avatar Joel Gingrich
# Project Layout
There are three main components to the server
- Controllers
- Services
- Requests / Responses (model)
## Controllers
The main role of a resource controller is to handle incoming requests that are being mapped to the controller. The controller then interacts with the model / service layer to accomplish the task that was specified by the request. Finally, the controller is responsible for converting and sending back data in it\'s expected format.
For this example we\'ll take a look at our ObjectiveController and break it down piece by piece
Below is the source code from `ObjectiveController.java`
```java
@RestController
public class ObjectiveController {
private final ObjectiveService objectiveService;
public ObjectiveController(ObjectiveService objectiveService) {
super();
this.objectiveService = objectiveService;
}
@PostMapping("/objective-complete")
public ResponseEntity<Object> authenticateObjective(@RequestBody ObjectiveRequest request) throws JsonProcessingException {
int response = objectiveService.completeObjective(request);
ObjectiveResponse responseObj = new ObjectiveResponse(response);
return new ResponseEntity<>(responseObj.toJSON(), HttpStatus.OK);
}
}
```
This class is mapping a Post request from `/objective-complete`, authenticates a specific external objective, and returns a response based on the success of that operation.
##### Component Annotation
To start, Spring requires annotation of components so the class requires a `@RestController` at the top of the class. This allows Spring to be aware of this component and automatically configure it.
##### Dependency Injection
Spring is capable of dependency injection, which is quite useful for unit testing and running in different configurations. Here we are using constructor based dependency injection as shown here with the Objective Service:
```java
private final ObjectiveService objectiveService;
public ObjectiveController(ObjectiveService objectiveService) {
super();
this.objectiveService = objectiveService;
}
```
##### Mapping URL Paths
Spring uses annotations such as:
- `@PostMapping`
- `@GetMapping`
- `@PutMapping`
- `@DeleteMapping`
to specify what type of request a method should be mapped to.
Using `@PostMapping("/objective-complete")` allows us to specify the specific path that the request should be coming from.
##### Making the Method
The annotation `@ReuestBody` in the method\'s parameters specify that the class `ObjectiveRequest` is the expected format of the incoming request\'s body. Spring Boot handles conversion from JSON to POJO automatically so if the request is properly formatted there should be no issues.
Moving on to the body of the method:
```java
int response = objectiveService.completeObjective(request);
ObjectiveResponse responseObj = new ObjectiveResponse(response);
return new ResponseEntity<>(responseObj.toJSON(), HttpStatus.OK);
```
We can see that the main goals of the controller are being accomplished. The service layer is being called with `objectiveService.completeObjective(request)` and the result is being formatted to match the expected response format and is then returned to the Spring servlet to be sent back to the user that made the request.
## Services
Service components in Spring Boot accomplish the business layer logic.
Service components have an interface and class that inherits said interface.
Looking again at external objectives, its service layer looks like this:
#### Objective Service Interface
```java
public interface ObjectiveService {
int completeObjective(ObjectiveRequest obj) throws IllegalObjectiveChangeException, DatabaseException, IllegalQuestChangeException;
}
```
The service interface is quite simple and just provides all the methods used by this service. No Spring Boot annotations are required for the interface.
#### Objective Service Implementation
```java
@Service
public class ObjectiveServiceImpl implements ObjectiveService {
private final ObjectiveStateTableDataGateway objectiveStateTableDataGateway;
public ObjectiveServiceImpl(ObjectiveStateTableDataGateway objectiveStateTableDataGateway) {
super();
this.objectiveStateTableDataGateway = objectiveStateTableDataGateway;
}
public ObjectiveServiceImpl(){
objectiveStateTableDataGateway = ObjectiveStateTableDataGatewayRDS.getSingleton();
}
@Override
public int completeObjective(ObjectiveRequest request) throws IllegalObjectiveChangeException, DatabaseException, IllegalQuestChangeException {
PlayerTokenManager ptm = PlayerTokenManager.getInstance();
int playerID = ptm.getPlayer(request.getPlayerID()).getPlayerID();
int questID = request.getQuestID();
int objectiveID = request.getObjectiveID();
try {
QuestManager.getSingleton().completeObjective(playerID, questID, objectiveID);
PlayerManager.getSingleton().getPlayerFromID(playerID).persist();
return 0;
} catch (Exception e){
return 1;
}
}
}
```
Note: `@Service` annotation is required in this class.
##### Dependency Injection (again)
Looking at the top of the Service implementation we can see constructor based dependency injection is being used again.
```java
private final ObjectiveStateTableDataGateway objectiveStateTableDataGateway;
public ObjectiveServiceImpl(ObjectiveStateTableDataGateway objectiveStateTableDataGateway) {
super();
this.objectiveStateTableDataGateway = objectiveStateTableDataGateway;
}
public ObjectiveServiceImpl(){
objectiveStateTableDataGateway = ObjectiveStateTableDataGatewayRDS.getSingleton();
}
```
This service component is dependent on a table data gateway for objective states, so we need to either pass in the gateway (for testing purposes) or use the default one.
##### The service logic
The method `public int completeObjective(ObjectiveRequest request)` contains the bussiness layer logic for completing an objective. This is called by the controller completes the requested action.
## Model
This section is very simple and just contains the POJO\'s that are used by the services / controllers. This includes the expected objects in requests and responses in the controller which are automatically converted to JSON by Spring Boot. These classes are not Spring Boot controllers and therefore don\'t need any annotations.
In order to use Mockito for proper Unit Testing, all objects here need `toJSON()`, `equals(Object o)`, and `hashCode()` methods.
These methods can be auto-generated in InteliJ by right clicking in the editor and selecting `generate` or using shortcut `alt+insert` then selecting the desired method to generate.
An example of properly generated methods:
#### Objective Information
```java
public class ObjectiveInformation {
//Class varibles, Constructor, Setters and getters are omitted for this example.
@Override
public String toString() {
return "ObjectiveInformation{" +
"description='" + description + '\'' +
", questID=" + questID +
", objectiveID=" + objectiveID +
'}';
}
public String toJSON() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ObjectiveInformation that = (ObjectiveInformation) o;
return questID == that.questID && objectiveID == that.objectiveID && Objects.equals(description, that.description);
}
@Override
public int hashCode() {
return Objects.hash(description, questID, objectiveID);
}
}
```