Core Web Application functionality using Java Spring MVC

Patroclos Lemoniatis
CodeX
9 min readOct 28, 2022

--

CRM, HRM, ERP are some application examples which make use of administration processes. These processes keep the business running, either by automatic processing, or by user interaction. Today most of these types of software ran as Web based applications.

Writing web applications is easier when using web application frameworks. These frameworks provide rapid application development.

Some of the best java frameworks

  • Spring
  • JSF (Java Server Faces)
  • GWT (Google Web Toolkit)
  • Struts
  • Vaadin

Lets talk about Spring framework !

“Spring makes building web applications fast and hassle-free. By removing much of the boilerplate code and configuration associated with web development, you get a modern web programming model that streamlines the development of server-side HTML applications, REST APIs, and bidirectional, event-based systems.”¹

Spring MVC and Spring Boot

Spring MVC is a Model-View-Controller framework. It is used to create scalable enterprise web applications. More complex than Spring Boot to setup and configure. If more flexibility is required with configuration, this is the best option.

Spring Boot is build on top of the Spring MVC. It minimizes the complexity of configuration and the boilerplate code that Spring MVC requires. It has build-in Jetty or Tomcat web server. It mostly used for rapid building of Microservice applications. Unlike Spring MVC, the layers it uses are, Presentation Layer, Data Access Layer, Service Layer, and Integration Layer.

Building a web based application back-office system

The Problem

But how to build a web application? What are the building blocks that make up the system?

The Break Down

To solve this problem, we will have to split the solution into the most basic parts.

  • Architecture design
  • Authentication — Authorization — Audit Log
  • User Interface

Architecture design

As an MVC framework, Spring uses a Model, View and Controller design approach. The Model is the grouping of all entities, data, rules and logic that make up the system. The View represents the User Interface like HTML, JSP pages etc… And of course the Controller, which accepts input and converts it to commands for the Model or meaningful user data for the View.

To further extend the code readability and maintainability, we will make use of the Domain Driven Design² in conjunction with the MVC design.

Layers of DDD Architecture

Starting from the bottom and going step by step up, lets see the Model in which the Domain Layer will reside. The ‘lowest-level’ class will be the entity, which represents a row from the database.

Example of Article Entity class which extends BaseO class. BaseO class extends BaseEntity class

All entities extend the parent abstract classes BaseO and BaseEntity, which hold the global ‘parent’ attributes. Please note BaseO and BaseEntity as abstract classes, cannot and should NOT be instantiated.

At the next ‘upper-level’, the ArticleBO will hold the behavior and business logic of the entity.

Example of ArticleBO which extends abstract BaseBO class

Business Object classes, extend the abstract BaseBO class in order to inherit the CRUD behavior load | save | delete (save method is used for both create and insert operations).

Please notice the 2 concrete methods in BaseBO class, loadBaseO() and saveBaseO(). Both methods hold the basic functionality for loading and persisting an entity into the database. Both methods call the global IRepository interface which holds the responsibility of the ORM (Hibernate) functionality for load/save. In this way we satisfy the Single Responsibility Principle⁴ and Dependency Inversion Principle

Coming up next, we will have the Service (Application) Layer. This layer orchestrates the lower-level object business logic.

Exampe CRUDService class which extends BaseService class
r@Transactional
public void save(BaseDTO input) throws Exception {
save(input, false);
}
public BaseDTO load(long id, Class<? extends BaseDTO> classType) throws Exception {
BaseBO baseBo = getBussinessBeanFromClassType(classType);
BaseO o = baseBo.load(id, CustomModelMapper.mapDTOClassToModelClass(classType));
if (o != null && o.getIsDeleted() == 1) return null;
return CustomModelMapper.mapModeltoDTO(o, classType);
}

Please notice the method save() takes DTO (Data Transfer Object) as input, since this is a Service (Application) Layer. Load() method takes entity Id as parameter and returns an Entity converted to a DTO.

Provided, the system will have complex business rules and logic, lets use the Facade³ design pattern and provide a greater level of orchestration and abstraction.

@Component
public class Facade {
@Autowired
private IProcessManager processManager;
@Autowired
private CRUDProcess CRUDProcess;
public <T> Object load(long id, Class<? extends BaseDTO> inputType) throws Exception{
IProcess<?, ?, ?> process = b -> CRUDProcess.load(id, inputType);
return processManager.runProcess(process, inputType, id, CRUDProcess.PROCESS_NAME_READ);
}
public Object delete(BaseDTO input) throws Exception{
IProcess<?, ?, ?> process = b -> CRUDProcess.delete(input);
return processManager.runProcess(process, input, CRUDProcess.PROCESS_NAME_DELETE);
}
public Object saveNew(BaseDTO input) throws Exception{
IProcess<?, ?, ?> process = b -> CRUDProcess.saveNew(input);
return processManager.runProcess(process, input, CRUDProcess.PROCESS_NAME_CREATE);
}
public Object saveUpdate(BaseDTO input) throws Exception{
IProcess<?, ?, ?> process = b -> CRUDProcess.saveUpdate(input);
return processManager.runProcess(process, input, CRUDProcess.PROCESS_NAME_UPDATE);
}
}

As you can see, the Facade class implements the CRUD functions and interacts with the corresponding Service (Application) Layer CRUD functions, by calling ProcessManager.runProcess() method.

@Component
public class ProcessManager implements IProcessManager {
@Autowired
private ActivityProcess ActivityProcess;
@Autowired
private IAuthenticationService AuthenticationService;
@Autowired
private UserService UserService;
public Object runProcess(IProcess<?, ?, ?> process, Class<? extends BaseDTO> inputType, BaseDTO input, Long id, String processName) throws Exception{Object result = null;if (processName == null )
{
throw new SystemException("ProcessName not defined");
}
var loggedUser = AuthenticationService.getLoggedDbUserDTO();if (loggedUser.getIsDeleted() == 1) {
throw new SystemException("Process authorization failed. User is deleted");
}
if (loggedUser.getEnabled() != 1) {
throw new SystemException("Process authorization failed. User is not enabled");
}
if(loggedUser.getRoles() == null) {
throw new SystemException("Process authorization failed. User has no defined roles");
}
var authorities = UserService.getUserAuthorities(loggedUser);if (authorities == null) {
throw new SystemException("Process authorization failed. No access permissions found");
}
var authoritiesList = authorities.stream().map(a -> a.getAuthority()).collect(Collectors.toList());if (!authoritiesList.contains(processName)) {
throw new SystemException("Process authorization failed. User has no access to this process. No access permissions found");
}
boolean isProcessSuccess = false;
String processError = "";
String processId = ProcessUtil.getActivityProcessId();
try
{
Thread.currentThread().setName(processId);
result = process.run(input);
isProcessSuccess = true;
}
catch (Exception e){
processError = e.getMessage();if (e.getCause() != null) processError = e.getCause().getMessage();
throw new SystemException(e);
}
finally {
//Handle process failure code ...
//reset thread's name after process and activity log insert is completeThread.currentThread().setName("");}return result;}}

Problem

Each process which is called from the Facade, needs to be authorized and handled, success or fail, with the same way across all the application.

Why don’t we call the Service (Application)Layer class method directly ?

We will need to write additional, duplicate code, each time a new process is added to the Facade class, resulting in possible implementation inconsistencies from the developers side. Plus we will have unnecessary boilerplate code.

Solution

Use Strategy Pattern

From the ProcessManager class above, we can pass the process implementation (IProcess) in the runProcess method().

Process Implementation

@Component
public class CRUDProcess extends BaseProcess {
@Autowired
private CRUDService CRUDService;
public final String PROCESS_NAME_CREATE = "CREATE";
public final String PROCESS_NAME_READ = "READ";
public final String PROCESS_NAME_UPDATE = "UPDATE";
public final String PROCESS_NAME_DELETE = "DELETE";
public final String PROCESS_NAME_CANCEL = "CANCEL";
public BaseDTO load(long id, Class<? extends BaseDTO> input) throws Exception {
BaseDTO dto = CRUDService.load(id, input);
return dto;
}
public Object delete(BaseDTO input) throws Exception {
CRUDService.delete(input);
return input;
}
public Object saveNew(BaseDTO input) throws Exception {
return save(input);
}
public Object saveUpdate(BaseDTO input) throws Exception {
return save(input);
}
public Object save(BaseDTO input) throws Exception {
CRUDService.save(input);
return CRUDService.load(input.getId(), input.getClass());
}
public Object cancel(BaseDTO input) {
return input;
}
}

Each process which is called from the Facade, is being authorized and handled, with success or failure, with the same way across all the application. In this way we satisfy the Open-Close Principle⁴ as well. Moreover, for any changes needed in the ProcessManager, are easy to be implemented as we need to modify the code only in one place.

Example Detail Page when loading Article DTO

Authentication — Authorization — Audit Log

For authentication, Spring Security Configuration is used

Sample Security Configuration SecurityFilterChain Bean

@EnableWebSecurity
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login", "/signup", "/signupconfirm").permitAll()
.antMatchers("/**", "/index**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/index?page=dashboard").successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailureHandler())
.and()
.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler()).invalidateHttpSession(true).deleteCookies("JSESSIONID")
.and()
.sessionManagement(session -> session
.maximumSessions(1) //set maximum concurrent user logins
.maxSessionsPreventsLogin(true) //second login will be prevented
)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).sessionFixation().migrateSession(); //on authentication create a new session to avoid session fixation attackhttp.csrf();http.headers()
.frameOptions()
.sameOrigin()
.xssProtection().block(false);
return http.build();}}

As we explained earlier, the Process Manager is authorizing each user’s permission to execute a process.

Additionally, from the Process Manager, Audit Logs are generated for every process call.

Additionally, Hibernate Envers, is being used, to keeps historical audit tables with the information of modified entity fields.

Example of an Audit Log for an edit made by user1 on Article entity

User Interface

Bootstrap version 5 is used for the frontend development of the pages. Thymeleaf is the preferred template engine in this project.

Thymeleaf is a modern server-side Java template engine for both web and standalone environments. Thymeleaf’s main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.⁵

As a high level explanation of the User Interface, the application consists of the Search and Detail pages.

Search Page

Example of Articles search page

Detail Page

Example of Article Detail page

Problem

Search and Detail Pages need to be consistent and also minimize the boilerplate code needed when developing new pages. Standard Components HTML code generation so they can be modified with the minimum effort.

Solution

Create a masterlayout.html page which is used as a parent-master page for all pages. This page will include all the css and javascripts used by the UI.

In extend, to standardize the search pages, we create a summarylayout.html page which is used a parent-master for the search pages. Which is also using the above masterlayout.html as parent layout.

In addition, to standardize the detail pages, we create a pagelayout.html page which is used a parent-master for the search pages. Which is also using the above masterlayout.html as parent layout.

Controllers, extend parent classes, as the example below of the ArticlesController (Articles search page).

Example of ArticlesController which extends SummaryController which extends BaseController

This way upgrading to a newer Bootstrap version and/or modifying CSS, Javascripts is made easy since they can be changed from one place.

Controller classes are extending behavior from parent classes, thus sharing common, reusable code to all the child subclasses.

Problem

How to change the look and feel of the entire application with the minimum effort?

Solution

Lets say we need to add a feature to all the input boxes across all the application. If we try to change all the input boxes from all the pages, this will take a considerable amount of time. So the approach we need to take is for the Components HTML code, to be generated from java code (UIComponent Classes), and added to the ModelAndView object, returned from the Controller class. In this way we achieve reusable and maintainable code which is easy to modify.

Making use of the thymeleaf templating, as explained above, all the pages extend a master page layout, thus is easy to modify the HTML, JSP pages along with the code changes.

For a working demo, please visit https://sightready.net:8443/SpringMVC/ . Register a new user and try it out!

--

--