Spring Boot — Project Structure Practices
In this article, I am going to talk about package structures for Spring Boot applications and give the one I create whenever I started a new project.
Proper packaging in a project is important because when the project grows or someone else is assigned to your project, it must be easy to get around different kind of files. For example, when you need a file of type model, you just directly go into the model package and easily find what you are looking for. If you do no have such a structure, it becomes a time-consuming problem in large projects.
So, let’s start with what type of packages we can have in a regular Spring Boot application and then look at the complete structure.
1- Model classes (model), one of the basic types we can see in almost every application. These classes represents data objects.
In Spring Boot, a model can be a domain model, which is a Java class that represents an entity or a concept in the application. For example, if you are building an e-Prescription application, you might have domain models like Doctor, Patient, or Prescription. These classes usually have fields, getters and setters, and sometimes methods that contain business logic.
package com.ePrescription.E_Prescription.System.model;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Document(collection = "patients")
public class Patient {
@Id
private String id;
private String name;
private String email;
private String medicalHistory;
private List<String> allergies;
// Constructors
public Patient() {}
public Patient(String name, String email, String medicalHistory, List<String> allergies) {
this.name = name;
this.email = email;
this.medicalHistory = medicalHistory;
this.allergies = allergies;
}
// Getters and Setters
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMedicalHistory() {
return medicalHistory;
}
public void setMedicalHistory(String medicalHistory) {
this.medicalHistory = medicalHistory;
}
public List<String> getAllergies() {
return allergies;
}
public void setAllergies(List<String> allergies) {
this.allergies = allergies;
}
@Override
public String toString() {
return "Patient{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", email='" + email + '\'' +
", medicalHistory='" + medicalHistory + '\'' +
", allergies=" + allergies +
'}';
}
}
2- Service classes (service), services are important parts of a Spring Boot application and they hold business logic. They will go under this package.
In Spring Boot, a service is a component that encapsulates the business logic of an application, often interacting with repositories for data access. Annotated with @Service, these components are Spring-managed beans that can be injected into controllers or other services, promoting loose coupling and modularity. Services handle core operations such as calculations, processing, and transactional tasks, ensuring that business rules are followed consistently across the application. By separating business logic from the controller (which handles HTTP requests) and the repository layer (which deals with data persistence), services enhance the reusability, testability, and maintainability of the code. This separation also allows for the business logic to be easily unit-tested, ensuring that the application remains robust and adaptable to changes.
package com.ePrescription.E_Prescription.System.service;
import com.ePrescription.E_Prescription.System.repository.PatientRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ePrescription.E_Prescription.System.model.Patient;
import java.util.List;
import java.util.Optional;
@Service
public class PatientService {
private final PatientRepository patientRepository;
@Autowired
public PatientService(PatientRepository patientRepository){
this.patientRepository = patientRepository;
}
public void addPatient(Patient patient){
patientRepository.save(patient);
}
public Optional<Patient> getPatientByID(String id){
return patientRepository.findById(id);
}
public List<Patient> getAllPatients(){
return patientRepository.findAll();
}
}
3 - Repository interfaces (repository), repositories provides data access abstraction and such interfaces especially the ones extending various Repository interfaces will go under this.
In Spring Boot, a repository is a component that handles data access, encapsulating the logic required to interact with the database. Annotated with @Repository
, it is part of the persistence layer and is often used to perform CRUD (Create, Read, Update, Delete) operations on entities. Repositories leverage Spring Data JPA to provide powerful database interaction capabilities without requiring extensive boilerplate code, allowing developers to focus on business logic. By extending interfaces like JpaRepository
or CrudRepository
, repositories automatically gain methods for common database operations, which can be further customized with query methods or native queries. This abstraction simplifies database interactions, promotes clean separation of concerns, and ensures that data access is handled efficiently and effectively within the application.
package com.ePrescription.E_Prescription.System.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.ePrescription.E_Prescription.System.model.Patient;
import org.springframework.stereotype.Repository;
@Repository
public interface PatientRepository extends MongoRepository<Patient, String> {
}
4 - Controller classes (controller), controllers are responsible for handling incoming requests and redirecting them to a suitable service.
In Spring Boot, a controller is a component that handles incoming HTTP requests, processes them, and returns the appropriate response, often by interacting with services. Annotated with @Controller
or @RestController
, controllers serve as the interface between the client (e.g., web browser, mobile app) and the application's backend, managing request routing, input validation, and response formatting. A @Controller
typically returns views (e.g., HTML pages), while a @RestController
returns data directly, often in JSON or XML format, for RESTful web services. Controllers ensure that user inputs are processed, business logic is invoked, and the results are appropriately communicated back to the client, making them a crucial part of the MVC (Model-View-Controller) architecture in Spring Boot applications.
package com.ePrescription.E_Prescription.System.controller;
import com.ePrescription.E_Prescription.System.service.PatientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.ePrescription.E_Prescription.System.model.Patient;
import javax.validation.Valid;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api")
public class PatientController {
@Autowired
private PatientService patientService;
@PostMapping("/addpatient")
public ResponseEntity<String> addPatient(@Valid @RequestBody Patient patient){
patientService.addPatient(patient);
return new ResponseEntity<>("Patient detail added successfully", HttpStatus.CREATED);
}
@GetMapping("/getpatient/{id}")
public Optional<Patient> getPatient(@PathVariable String id){
return patientService.getPatientByID(id);
}
@GetMapping("/getallpatients")
public ResponseEntity<List<Patient>> getAllPatients(){
List<Patient> patients = patientService.getAllPatients();
return new ResponseEntity<>(patients, HttpStatus.OK);
}
}
5 - Configuration classes (configuration), every configuration classes can go under this. SecurityConfiguration can be of them if you apple security in your application.
In Spring Boot, a configuration class is used to define beans and settings for the application, typically serving as a central place to configure various aspects of the Spring framework. Annotated with @Configuration
, this class indicates that it provides configuration settings, often replacing traditional XML-based configuration files. In the provided example, the SecurityConfig
class is annotated with both @Configuration
and @EnableWebSecurity
, signifying that it configures web security for the application. Within this class, the securityFilterChain
method is annotated with @Bean
, indicating that it defines a Spring bean—a SecurityFilterChain
—which configures HTTP security settings. This includes disabling CSRF protection (typically for development purposes), allowing all requests under the /api/**
path to bypass authentication, and requiring authentication for all other requests. This approach centralizes security configurations, making it easier to manage and modify security policies across the application.
package com.ePrescription.E_Prescription.System.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Disable CSRF (use cautiously, only in development)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**").permitAll() // Use requestMatchers instead of antMatchers
.anyRequest().authenticated() // All other requests need authentication
);
return http.build();
}
}
Application.Properties
spring.application.name=E-Prescription-System
spring.data.mongodb.uri=mongodb+srv://username:password@cluster0.glbbsog.mongodb.net/ePrescription?retryWrites=true&w=majority&appName=Cluster0
spring.security.user.name=admin
spring.security.user.password=admin
Testing in Postman using these details
Below are examples of JSON data that you can use in Postman to test the various endpoints of your `PatientController`.
### 1. **POST `/api/addpatient`**
— **Purpose**: Add a new patient.
— **Method**: POST
— **Sample JSON Data**:
```json
{
“id”: “12345”,
“name”: “John Doe”,
“age”: 30,
“gender”: “Male”,
“address”: “123 Main St, Springfield”,
“phone”: “555–1234”,
“email”: “johndoe@example.com”
}
```
— **Response**:
```json
“Patient detail added successfully”
```
### 2. **DELETE `/api/deletepatient/{id}`**
— **Purpose**: Delete a patient by ID. (Assuming a DELETE endpoint is available in your service)
— **Method**: DELETE
— **URL**: `/api/deletepatient/12345`
— **Response**:
```json
“Patient deleted successfully”
```
### 3. **PUT `/api/updatepatient/{id}`**
— **Purpose**: Update an existing patient’s details. (Assuming a PUT endpoint is available in your service)
— **Method**: PUT
— **URL**: `/api/updatepatient/12345`
— **Sample JSON Data**:
```json
{
“name”: “John Doe”,
“age”: 31,
“gender”: “Male”,
“address”: “456 Elm St, Springfield”,
“phone”: “555–5678”,
“email”: “john.doe@newemail.com”
}
```
— **Response**:
```json
“Patient updated successfully”
```
### 4. **GET `/api/getpatient/{id}`**
— **Purpose**: Retrieve a patient’s details by ID.
— **Method**: GET
— **URL**: `/api/getpatient/12345`
— **Response**:
```json
{
“id”: “12345”,
“name”: “John Doe”,
“age”: 30,
“gender”: “Male”,
“address”: “123 Main St, Springfield”,
“phone”: “555–1234”,
“email”: “johndoe@example.com”
}
```
### 5. **GET `/api/getallpatients`**
— **Purpose**: Retrieve details of all patients.
— **Method**: GET
— **URL**: `/api/getallpatients`
— **Response**:
```json
[
{
“id”: “12345”,
“name”: “John Doe”,
“age”: 30,
“gender”: “Male”,
“address”: “123 Main St, Springfield”,
“phone”: “555–1234”,
“email”: “johndoe@example.com”
},
{
“id”: “67890”,
“name”: “Jane Smith”,
“age”: 28,
“gender”: “Female”,
“address”: “789 Oak St, Springfield”,
“phone”: “555–6789”,
“email”: “janesmith@example.com”
}
]
```
These JSON samples should help you effectively test the CRUD operations of your `PatientController` in Postman. Make sure to adapt the URLs and fields according to your actual API implementation and data model.