Skip to content

johannpando/codechallenge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Code Challenge

Introduction

The objective of the project is to create API-REST services with Spring Boot

Getting Started

Prerequisites

Java

The java version used in this project is at least version 8

Maven

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.

Lombok

The project uses Lombok. Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.

The project was carried out with the eclipse IDE, so it was necessary to install Lombok. The download and installation can be obtained from this link:

Technologies

  • Spring Boot 2.1.4.RELEASE - Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run"
  • Lombok - Project Lombok is a small library that can be used to reduce the amount of boilerplate Java code that is commonly written for Java classes
  • H2 Database - H2 database can be configured to run as inmemory database, which means that data will not persist on the disk
  • Swagger 2 - Using Swagger makes documenting your RESTful services much easier
  • Maven - Maven is a software tool for project management and construction
  • Java 8 - Java is an object-oriented programming language

Configurations

H2 Database

We decided to use H2 as a database in memory due to its characteristics such as its integration with Spring Boot. In order to configure H2 database correctly, the following properties had to be added in the application.properties configuration file:

spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Swagger 2

We configure swagger by adding a configuration class, here the example:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2).select()
				.apis(RequestHandlerSelectors.basePackage("com.orangebank.codechallenge")).paths(PathSelectors.any())
				.build();
	}
}

Deployment and run tests

Built With

At the root of the project:

mvn clean install

This command attempts to clean the files and directories generated by Maven during its build. The Install Plugin is used during the install phase to add artifact(s) to the local repository. Before the construction and generation of the project all the tests will be executed and the jar will only be generated if these tests are executed successfully.

Run the application

Running as a Packaged Application

You can run your application using java -jar, as shown in the following example:

$ java -jar target/orangebank-codechallenge-0.0.1-SNAPSHOT.jar

Using the Maven Plugin

The Spring Boot Maven plugin includes a run goal that can be used to quickly compile and run the application. The following example shows a typical Maven command to run a Spring Boot application:

$ mvn spring-boot:run

Host and Port

The application runs over port number 9000:

http://localhost:9000

Testing the application

Postman

Postman is a HTTP client that allows us to test our endpoints.

Some requests are added so that they can be imported and executed in Postman.

Curl

Here an example:

curl -X POST \
  http://localhost:9000/api/transactions \
  -H 'Accept: */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Length: 221' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: JSESSIONID=6A7916BF24330FDA19E815FC39D35E99; JSESSIONID.091d8ed4=node018k7jagmg4wap1l4duvpxmc21n0.node0' \
  -H 'Host: localhost:9000' \
  -H 'Postman-Token: fd534148-de64-4111-bcaa-4df326f8e406,048ff1a5-6b82-4785-8faf-f3a5905c7b9a' \
  -H 'User-Agent: PostmanRuntime/7.15.2' \
  -H 'cache-control: no-cache' \
  -d '{
    "reference": "123456A",
    "account_iban": "ES9820385778983000760236",
    "date": "2019-07-16T16:55:42.000Z",
    "amount": 7643,
    "fee": 3.18,
    "description": "Restaurant payment",
    "channel": "CLIENT"
}'

Considerations

Validations

By default, Spring Boot will get and download the Hibernate Validator automatically. Annotate the bean with javax.validation.constraints.* annotations.

import java.util.Date;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@ToString
public class RequestBodyCustom {

	private String reference;

	@JsonProperty("account_iban")
	@NotEmpty(message = "account_iban is mandatory")
	private String accountIban;

	private Date date;

	@NotNull(message = "amount is mandatory")
	private Double amount;

	private Double fee;

	private String description;

	private String channel;

	private String status;
}

Add @Valid to @RequestBody. Bean validation is enabled now

@RestController
@RequestMapping(path = "api", produces = { MediaType.APPLICATION_JSON_VALUE })
@Validated
@Api(value = "This is the controller for the application")
public class TransactionController {

	private TransactionCreateService transactionCreateService;

	private TransactionSearchService transactionSearchService;

	@JsonView(View.Summary.class)
	@PostMapping("/transactions")
	@ApiOperation(value = "Create a transaction", notes = "If any error occurs, it will be intercepted by an error handler")
	public ResponseEntity<ResponseBodyCustom> createTransaction(@RequestBody @Valid RequestBodyCustom request) {
		TransactionDTO transactionDTO = requestToDto(request);
		TransactionDTO t = transactionCreateService.createTransaction(transactionDTO);
		ResponseBodyCustom response = new ResponseBodyCustom();
		response.setMessage("Transaction created");
		response.setStatus(HttpStatus.OK);
		response.setTimestamp(LocalDateTime.now());
		List<TransactionDTO> transactions = new ArrayList<>();
		transactions.add(t);
		response.setTransactions(transactions);
		return ResponseEntity.ok().body(response);
	}

Exceptions

By default, Spring Boot provides a BasicErrorController controller for /error mapping that handles all errors, and getErrorAttributes to produce a JSON response with details of the error, the HTTP status, and the exception message. In Spring Boot, we can use @ControllerAdvice to handle custom exceptions, here the example:

@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

	private static final String ERRORS = "errors";
	private static final String STATUS = "status";
	private static final String TIMESTAMP = "timestamp";
	private static final String BAD_REQUEST = "BAD_REQUEST";

	@ExceptionHandler(ConstraintViolationException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST)
	@ResponseBody
	public final ResponseEntity<ErrorResponse> handleConstraintViolation(ConstraintViolationException ex,
			WebRequest request) {
		List<String> details = ex.getConstraintViolations().stream().map(e -> e.getMessage())
				.collect(Collectors.toList());

		ErrorResponse error = new ErrorResponse(BAD_REQUEST, details);
		return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
	}

The Service Layer

The project has two characteristics of CRUD operations, Read and Create / Update. The service layer could have defined both characteristics but we have decided to separate them and create an interface (and their implementation) for each of them.

Therefore, the structure is as follows:

  • com.orangebank.codechallenge.service
    • TransactionCreateService.java
    • TransactionCreateServiceImpl.java
    • TransactionSearchService.java
    • TransactionSearchServiceImpl.java

Business Rules

To carry out the business test cases we have decided to use black box tests using Apache HttpClient to send HTTP GET/POST request.

All business cases are in class /orangebank-codechallenge/src/test/java/com/orangenank/codechallenge/acceptance/AcceptanceTest.java, here an example:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrangebankCodechallengeApplication.class, webEnvironment = WebEnvironment.DEFINED_PORT)
public class AcceptanceTest {

	@LocalServerPort
	private Integer port;

	/**
	 * @Given: A transaction that is not stored in our system.
	 * @When: I check the status from any channel
	 * @Then: The system returns the status 'INVALID'
	 */
	@Test
	public void givenNotTransactionBD_whenStatusAnyChannel_thenReturnINVALIDStatus() {
		try {
			String uri = "http://localhost:".concat(port.toString()).concat("/api/transactions/status/XXX111/CLIENT");
			HttpGet request = new HttpGet(uri);
			HttpClient httpclient = HttpClientBuilder.create().build();
			HttpResponse response = httpclient.execute(request);
			BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
			StringBuilder result = new StringBuilder();
			String line = "";
			while ((line = rd.readLine()) != null) {
				result.append(line);
			}

			String jsonExpected = "{\"reference\":\"XXX111\",\"status\":\"INVALID\"}";
			JSONObject jsonResult = new JSONObject(result.toString());

			JSONAssert.assertEquals(jsonExpected, jsonResult, false);

		} catch (Exception e) {
			fail();
		}
	}

Releases

No releases published

Packages

 
 
 

Contributors

Languages