![]() |
- Doe opens the chat page to communicate with his friends.
- He is prompted to choose a nickname.
- He enters the chat page and sends a message. The message is sent to the Spring MVC endpoint to be saved to the database and broadcast.
- The specified endpoint handles the message and broadcasts that message to all clients connected to the chat system.
Before proceeding with the internal structure of the project, let me explain which libraries we will use for the project features listed above, and manage them by using Gradle. When you clone the project from GitHub, you will see a file called build.gradle in the project root directory as below.
- buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.4.RELEASE")
- }
- }
- apply plugin: 'java'
- apply plugin: 'eclipse'
- apply plugin: 'idea'
- apply plugin: 'spring-boot'
- apply plugin: 'war'
- jar {
- baseName = 'realtime-chat'
- version = '0.1.0'
- }
- war {
- baseName = 'ROOT'
- }
- sourceCompatibility = 1.7
- targetCompatibility = 1.7
- repositories {
- mavenCentral()
- }
- sourceCompatibility = 1.7
- targetCompatibility = 1.7
- dependencies {
- providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
- compile("org.springframework.boot:spring-boot-starter-web")
- compile("org.springframework.boot:spring-boot-starter-thymeleaf")
- compile("org.springframework.boot:spring-boot-starter-data-mongodb")
- compile("org.springframework.boot:spring-boot-starter-websocket")
- compile("org.springframework:spring-messaging")
- testCompile("junit:junit")
- }
- task wrapper(type: Wrapper) {
- gradleVersion = '2.3'
- }
In order to generate a war file, we have used apply plugin: 'war'. Modulus also expects the war name to be ROOT.war by default, and that is why we have used:
- war {
- baseName: 'ROOT.war'
- }
That is all for the project dependencies section, and you can refer to the Gradle user guide for more about Gradle.
3. Software Design
If you want to develop a good application, it is best practice to define your project structure in small pieces. You can see the pieces of the entire architecture of our application.
3.1. Model
We are developing a chat application, so we can say that we have a ChatMessageModel model (i.e. domain object). While we are saving or viewing the chat message detail, we can cast the chat object from or to this ChatMessageModel model. Also, we can use the User model for chat users, but to make the application simpler, we will use just nickname as text. The ChatMessageModel model has the following fields: text, author, and createDate. The class representation of this model is as follows:
- package realtime.domain;
- import org.springframework.data.annotation.Id;
- import java.util.Date;
- /**
- * @author huseyinbabal
- */
- public class ChatMessageModel {
- @Id
- private String id;
- private String text;
- private String author;
- private Date createDate;
- public ChatMessageModel() {
- }
- public ChatMessageModel(String text, String author, Date createDate) {
- this.text = text;
- this.author = author;
- this.createDate = createDate;
- }
- public String getText() {
- return text;
- }
- public void setText(String text) {
- this.text = text;
- }
- public String getAuthor() {
- return author;
- }
- public void setAuthor(String author) {
- this.author = author;
- }
- public Date getCreateDate() {
- return createDate;
- }
- public void setCreateDate(Date createDate) {
- this.createDate = createDate;
- }
- @Override
- public String toString() {
- return "{" +
- "\"id\":\"" + id + '\"' +
- ",\"text\":\"" + text + '\"' +
- ",\"author\":\"" + author + '\"' +
- ",\"createDate\":\"" + createDate + "\"" +
- '}';
- }
- }
3.2. Controller
The controller is the behavior of your application. This means you need to keep your controller simple and capable of easy interaction with domain models and other services. We are expecting our controllers to handle:
- Chat message save requests
- Listing the latest chat messages
- Serving the chat application page
- Serving the login page
- Broadcasting chat messages to clients
- package realtime.controller;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.domain.PageRequest;
- import org.springframework.data.domain.Sort;
- import org.springframework.http.HttpEntity;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
- import org.springframework.messaging.handler.annotation.MessageMapping;
- import org.springframework.messaging.handler.annotation.SendTo;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import realtime.domain.ChatMessageModel;
- import realtime.message.ChatMessage;
- import realtime.repository.ChatMessageRepository;
- import java.util.Date;
- import java.util.List;
- /**
- * @author huseyinbabal
- */
- @Controller
- public class ChatMessageController {
- @Autowired
- private ChatMessageRepository chatMessageRepository;
- @RequestMapping("/login")
- public String login() {
- return "login";
- }
- @RequestMapping("/chat")
- public String chat() {
- return "chat";
- }
- @RequestMapping(value = "/messages", method = RequestMethod.POST)
- @MessageMapping("/newMessage")
- @SendTo("/topic/newMessage")
- public ChatMessage save(ChatMessageModel chatMessageModel) {
- ChatMessageModel chatMessage = new ChatMessageModel(chatMessageModel.getText(), chatMessageModel.getAuthor(), new Date());
- ChatMessageModel message = chatMessageRepository.save(chatMessage);
- List<ChatMessageModel> chatMessageModelList = chatMessageRepository.findAll(new PageRequest(0, 5, Sort.Direction.DESC, "createDate")).getContent();
- return new ChatMessage(chatMessageModelList.toString());
- }
- @RequestMapping(value = "/messages", method = RequestMethod.GET)
- public HttpEntity list() {
- List<ChatMessageModel> chatMessageModelList = chatMessageRepository.findAll(new PageRequest(0, 5, Sort.Direction.DESC, "createDate")).getContent();
- return new ResponseEntity(chatMessageModelList, HttpStatus.OK);
- }
- }
As you can see, there are two types of endpoint /messages: GET and POST. When you make a POST request to endpoint /messages with proper message payload, it will be automatically cast to the ChatMessageModel class, and the message will be saved to MongoDB. After successful saving, it will be automatically pushed to the clients. But, how? In that action, there is an annotation @SendTo("/topic/newMessage"). This will send the content returned from the function to the clients. And the returned content is like below:
- ...
- return new ChatMessage(chatMessageModelList.toString());
- ...
- This is the latest message from the database:
![]() |
The above message will be converted to a format for WebSocket communication. This channel message will be handled on the client side with a third-party JavaScript library, and it will be handled in the following sections.
For message db operations, spring-boot-starter-data-mongodb is used. This library helps us for repository operations, and to create a repository object for MongoDB is very simple. You can see the example ChatMessageRepository below:
- package realtime.repository;
- import org.springframework.data.mongodb.repository.MongoRepository;
- import realtime.domain.ChatMessageModel;
- import java.util.List;
- /**
- * @author huseyinbabal
- */
- public interface ChatMessageRepository extends MongoRepository<ChatMessageModel, String> {
- List<ChatMessageModel> findAllByOrderByCreateDateAsc();
- }
If you create an interface and extend MongoRepository<?, String>, you will be able to automatically use CRUD operations like find(), findAll(), save(), etc.
As you can see, MongoRepository expects a domain object. We have already defined this model in the Model section of the tutorial. In this repository, we have defined a custom function called findAllByOrderByCreateDateAsc().
If you have ever used JPA before you can understand this easily, but let me explain this briefly. If you define a function name in an interface that extends MongoRepository, this function name will be parsed to a query on the back end by Spring automatically. It will be something like:
- SELECT * FROM ChatMessageModel WHERE 1 ORDER BY createDate ASC
In ChatMessageController, we used this function, and also we have used the default functions of the MongoRepository:
- chatMessageRepository.findAll(new PageRequest(0, 5, Sort.Direction.DESC, "createDate")).getContent()
findAll is used a parameter for sorting and pagination. You can have a look at the guide on the Spring website for more details about Spring JPA.
Written by Hüseyin Babal
If you found this post interesting, follow and support us.
Suggest for you:
No comments:
Post a Comment