Skip to content

gtulipani/chat-service

Repository files navigation

Build Status

chat-service

Description

Microservice that acts as a Backend Service for a Chat.

Building and Running

Build

Locally using maven

The standard way to build the application is with the following Maven command in the root path:

mvn clean install -DskipTests

Take into account that the previous command doesn't include running the UTs. These can be by omitting the -DskipTests parameter or by executing the following command:

mvn test

After executing the tests, a HTML report is generated using surefire plugin under path:

target/surefire-reports/emailable-report.html

Locally using Dockerfile

There is an alternative way to build the application using a Dockerfile. The following command must be executed in the root path:

mvn clean package dockerfile:build

Run

Locally using maven

The standard way to run the application is with the following maven command:

mvn spring-boot:run

Take into account that a MySQL Service running is required before executing that command.

Locally using Docker

There is an alternative way to run the application with Docker. It provides an isolated environment where each service can be started on different containers. The repo includes a docker-compose file, which includes both the Application (exposed in port 8080) and the MySQL Database (33060). The following command must be executed in the root path:

docker-compose up

Locally using Kubernetes

There is another alternative way to run the application with Kubernetes. It provides another abstraction layer from Docker or any other container-oriented application. The repo includes 3 different manifests:

  • mysql-volume.yaml: PersistentVolume for the MySQL Database consumed by the service.
  • mysql.yaml: Service and Deployment from the MySQL DB. Also a Secrets is included to handle MySQL credentials.
  • chat-service.yaml: Service and Deployment for the chat-service. It's configured to support 2 replicas and exposes the port 30161. Also both readinessProbe and livenessProbe have been configured using the /health endpoint.

The manifests must be executed in the same order as listed above to avoid issues. Each one of them must be started with the kubectl command:

kubectl apply -f mysql-volume.yaml
kubectl apply -f mysql.yaml
kubectl apply -f chat-service.yaml

The services status can be easily verified using the Kubernetes Dashboard. The result should be similar to:

Technologies

Spring

The Application is a standard Spring Boot Application and it's conformed by the following layers:

  • AuthenticationTokenInterceptor: HandlerInterceptor to verify authentication for the requests
  • Controller: handles all the incoming requests from the outside world. There are three of them: MessageController, UserController and LoginController.
  • Service: handles all the logic from the incoming requests from the controllers. There are also three of them: MessageService, UserService and LoginService.
  • Repository: CrudRespository that handles the last communication between the services and the Database. There are two of them: MessageRepository and UserRepository.

MySQL

The chosen Database Engine is MySQL, a free relational DB model. By the moment it contains only two table called users and messages. The first one contains the following columns:

id|created_on|last_modified|usernane|password|token|

The second one contains the following columns:

id|created_on|last_modified|sender|recipient|content|

Flyway

Flyway is a database migration tool similar to Liquibase. It is recommended by Spring Boot. See the documentation. The migrations scripts are in SQL directly.

The project is configured to run the migration scripts on start.

Bearer Token Authentication

The /messages endpoint is authenticated with Bearer Token Authorization. The token is provided as part of the response of the /login API and it doesn't expire at the moment. If no token is found as part of these requests, or an incorrect one is found instead, a 401 (Unauthorized) Error Message is returned.

Orika Mapping

Orika is a framework used to convert different entities on a extensible, clear and practical way. We have defined a CustomMapperStrategy that acts as a CustomMapper among with a Strategy pattern, in order to be able to register them programatically (take a look at MessageMapper). The mappers reduce the work necessary to convert between the necessary entities. E.g. to convert between a entity of type MessageCreationRequest to an entity of type Message, we just need to:

messageMapper.map(messageCreationRequest, Message.class)

AES Encryption Service

Advanced Encryption Standard is a subset of Rijndael block cypher with many intermediate steps used to encrypt data using an encryption key and an initialization vector. These two values are included in the application.yaml, but could be easily overriden on each environment, so that the data remains secured on each particular environment. A Service has been created that implements this algorithm, in order to securely store all sensitive data (password and token).

Sensitive Field Converter

A JPA AttributeConverter has been created in order automatically:

  • Encrypt all sensitive fields before storing them in the DB
  • Decrypt all sensitive fields after retrieving them from the DB

ObjectMother Pattern

The ObjectMother is a Factory class used only for Unit Tests, and it's really useful for avoiding duplicating code and to gain consistency across different classes under test. The main advantage of it is that we can avoid duplicate code and the final result is a cleaner code block. Example from ObjectMother implementations

@Test
	public void testCreateMessageText() {
		MessageCreationRequest messageCreationRequest = aTextMessageCreationRequest();
		Message message = aCompleteTextMessageWithoutId();
		Message messageWithId = aCompleteTextMessage();
		MessageCreationResponse messageCreationResponse = aMessageCreationResponseEntity();
		when(messageMapper.map(messageCreationRequest, Message.class)).thenReturn(message);
		when(messageRepository.save(message)).thenReturn(messageWithId);
		when(messageMapper.map(messageWithId, MessageCreationResponse.class)).thenReturn(messageCreationResponse);

		MessageCreationResponse response = messageService.createMessage(messageCreationRequest);

		assertThat(response).isEqualTo(messageCreationResponse);
	}

Postman Collection

A Postman Collection has been included in the repository, containing all the existing endpoints among with examples:

GET /messages

API to get all the messages for a given recipient starting with a given messageId (or the next one lower to it). It accepts three query parameters: recipient, start and limit. The last parameter is optional and its default value is 100. The response includes all the messages with the recipient passed as parameter starting from the messageId defined with the start parameter:

{
    "messages": [
        {
            "id": 1,
            "timestamp": "2019-03-12T02:52:52.000+0000",
            "sender": 1,
            "recipient": 2,
            "content": {
                "type": "text",
                "text": "Example message"
            }
        }
    ]
}

POST /messages

API to create a new message. The id from the message among with the timestamp is returned as part of the response. Expected input payload:

{
    "sender": 1,
    "recipient": 2,
    "content": {
        ...
    }
}

Example response payload:

{
    "id": 1,
    "timestamp": "2019-03-12T02:55:38.187+0000"
}

Three different type of messages can be created: text, image or video, each one of them with different content format.

Text Message

The content contains only a text field:

"content": {
    "type": "text",
    "text": "Example message"
}

Image Message

The content contains url, height and width fields:

"content": {
    "type": "image",
    "url": "https://media.licdn.com/dms/image/C4E0BAQF768b2Tj5qQQ/company-logo_200_200/0?e=2159024400&v=beta&t=BiHrNL31_Jj1o2-tRifxoFXWeYhkfM-Xu4mcCPkQ6j0",
    "height": 200,
    "width": 200
}

Video Message

The content contains url and source fields:

"content": {
    "type": "video",
    "url": "https://www.youtube.com/watch?v=X3paOmcrTjQ",
    "source": "youtube"
}

POST /users

API to create a new user with username and password in the system. The id from the user is returned as part of the payload. Example input payload:

{
    "username": "gtulipani",
    "password": "password"
}

Example response payload:

{
    "id": "1"
}

POST /login

API to login with an existing username and password. A token is returned as part of the payload, and can be user for the subsequent authenticated APIs. Example input payload:

{
    "username": "gtulipani",
    "password": "password"
}

Example response payload:

{
    "id": 1,
    "token": "Z19uvknyEbFmr3s7qkuPj"
}

CI

Travis CI has been chosen as CI Software. It's already configured and runs all the UT for every PR and Build (including master). The status can be found at the top of this file.

More Stats

The project includes a total of 103 Unit Tests. The total Coverage is 94%, where all classes have 100% coverage, except from Application.java (main Spring Boot Application class) and the ones from the Model itself (contain only Getters, Setters, Constructors and Builder).

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published