DEV Community

bseayin
bseayin

Posted on

Practical Implementation of the Observer Pattern: Elegant Update Mechanism in Spring Boot

Practical Implementation of the Observer Pattern: Elegant Update Mechanism in Spring Boot

Introduction

In numerous application systems, we often encounter the challenge of synchronizing updates across multiple related tables. For instance, in an educational management system, when the age field in the student's personal information table changes, we might also need to update corresponding records in the student's profile and academic achievement tables. This article will present a concrete case study on how to utilize the Observer Pattern in a Spring Boot project to address this requirement gracefully.

Overview of the Observer Pattern

The Observer Pattern is a software design pattern that establishes a one-to-many relationship among objects, ensuring that all dependents are notified and automatically updated when the state of an object changes. Within the Spring framework, the Observer Pattern is typically implemented through event-driven mechanisms.

Case Scenario

Let us consider a Spring Boot project for an educational management system, encompassing three primary data tables:

  1. Students Table: Stores personal details of students, including age.
  2. Student Records Table: Maintains records of student profiles.
  3. Student Scores Table: Keeps track of student academic scores.

Our objective is to automatically trigger updates in the student records and scores tables when the age field in the students table is modified.

Technology Stack

  • Java 11
  • Spring Boot 2.x
  • Spring Data JPA

Implementation Steps

Step 1: Define the Event

Initially, we need to create an event class that signifies the update of a student's age.

import org.springframework.context.ApplicationEvent;

public class StudentAgeUpdateEvent extends ApplicationEvent {

    private static final long serialVersionUID = 1L;

    private final Long studentId;
    private final int newAge;

    public StudentAgeUpdateEvent(Object source, Long studentId, int newAge) {
        super(source);
        this.studentId = studentId;
        this.newAge = newAge;
    }

    public Long getStudentId() {
        return studentId;
    }

    public int getNewAge() {
        return newAge;
    }
}
Enter fullscreen mode Exit fullscreen mode
Step 2: Create Listeners

Subsequently, we must establish two listener classes to monitor the StudentAgeUpdateEvent and update the student records and scores tables upon the event's occurrence.

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class StudentRecordUpdater implements ApplicationListener<StudentAgeUpdateEvent> {

    private final StudentRecordRepository studentRecordRepository;

    public StudentRecordUpdater(StudentRecordRepository studentRecordRepository) {
        this.studentRecordRepository = studentRecordRepository;
    }

    @Override
    public void onApplicationEvent(StudentAgeUpdateEvent event) {
        Long studentId = event.getStudentId();
        int newAge = event.getNewAge();

        // Update the student records table
        studentRecordRepository.updateAge(studentId, newAge);
    }
}
Enter fullscreen mode Exit fullscreen mode
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class StudentScoreUpdater implements ApplicationListener<StudentAgeUpdateEvent> {

    private final StudentScoreRepository studentScoreRepository;

    public StudentScoreUpdater(StudentScoreRepository studentScoreRepository) {
        this.studentScoreRepository = studentScoreRepository;
    }

    @Override
    public void onApplicationEvent(StudentAgeUpdateEvent event) {
        Long studentId = event.getStudentId();
        int newAge = event.getNewAge();

        // Update the student scores table
        studentScoreRepository.updateAge(studentId, newAge);
    }
}
Enter fullscreen mode Exit fullscreen mode
Step 3: Publish the Event

Within the student service layer, we need to publish the StudentAgeUpdateEvent after an age field update.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class StudentService {

    private final ApplicationEventPublisher eventPublisher;

    @Autowired
    public StudentService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void updateStudentAge(Long studentId, int newAge) {
        // Update the student's age in the database
        // ...

        // Publish the event
        eventPublisher.publishEvent(new StudentAgeUpdateEvent(this, studentId, newAge));
    }
}
Enter fullscreen mode Exit fullscreen mode
Step 4: Transaction Management

To ensure data consistency and integrity, we should include the @Transactional annotation in the StudentService to guarantee that all update operations occur within a single transaction.

import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class StudentService {
    // ... previous code ...

    public void updateStudentAge(Long studentId, int newAge) {
        // ... previous code ...
    }
}
Enter fullscreen mode Exit fullscreen mode

By employing the Observer Pattern in this manner, we can create a robust and scalable solution for managing data synchronization across related tables in a Spring Boot application. This technique not only simplifies the codebase but also enhances the reliability and efficiency of the system by leveraging Spring's built-in event handling capabilities.

Top comments (0)