Activity-fields in this context are being used to refer to fields such as created_at
, created_by
, updated_at
and updated_by
that are common in any entity for which CRUD APis are exposed
- JPA's
- @Embedded
- @Embeddable
- @PrePersist
- @PreUpdate
- ThreadLocal.class
- Http Request Filters (OncePerRequestFilter.class)
can ⭐️ Repository if found helpful or leave comments in Discussions 💬😄
- Activity.class
- Patient.class
- LoggedInDoctorDetailProvider.class
- LoggedInDoctorDetailStorageFilter.class
- SecurityConfiguration.class
- Migration .sql file
For every entity for which CRUD operation is performed, there are some columns that are repeated for all of them (created_at, created_by etc are used in this POC). In every POST and PUT operation the developer has to expicility provide values in the columns.
EXAMPLE : If a table contains 5 columns, full_name
and the 4 activity columns (mentioned above), the code in the service layer would look like the one below:
var user = new User();
user.setFullName("Whatever came from frontend");
user.setCreatedAt(current-time);
user.setCreatedBy(id-from-decoding-JWT);
user.setUpdatedAt(current-time);
user.setUpdatedBy(id-from-decoding-JWT);
db.save(user);
The goal of this POC is to convert the above code to the one below, with the values in the remaining column being filled automatically:
var user = new User();
user.setFullName("Whatever came from frontend");
db.save(user);
while the migration scipt remains the same 😄
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT UUID(),
full_name VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL,
created_by UUID NOT NULL,
updated_at TIMESTAMP NOT NULL,
updated_by UUID NOT NULL
);
- Create a class
Activity
and annotate it with@Embeddable
, define the columns that are to be kept common in the required entities. Using @PrePersist and @PreUpdate we'll take care of putting the values in the 4 fields.
@Embeddable
@Data
public class Activity {
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "created_by", nullable = false, updatable = false)
private UUID createdBy;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@Column(name = "updated_by", nullable = false)
private UUID updatedBy;
}
- Using
@Embedded
annotation, define an instance of the above created class in the Entity classes
@Entity
@Table(name = "patients")
@Data
public class Patient implements Serializable {
private static final long serialVersionUID = 7906541761495255102L;
@Id
private UUID id;
@Column(name = "full_name", nullable = false, length = 100)
private String fullName;
@Embedded
@Setter(AccessLevel.NONE)
private Activity activity = new Activity();
}
- Taking care of
created_at
andupdated_at
(timesatmp fields) using JPA's@PrePersist
and@PreUpdate
inside the Activity class created in the first step
@PrePersist
void onCreate() {
this.createdAt = LocalDateTime.now(ZoneId.of("+00:00"));
this.updatedAt = LocalDateTime.now(ZoneId.of("+00:00"));
}
@PreUpdate
void onUpdate() {
this.updatedAt = LocalDateTime.now(ZoneId.of("+00:00"));
}
- Defining a class with an instance of ThreadLocal.class to store primary-id of logged-in user with
Getters
andSetters
public class LoggedInDoctorDetailProvider {
private static final ThreadLocal<UUID> userId = new ThreadLocal<UUID>();
public static void setUserId(final UUID id) {
userId.set(id);
}
public static UUID getId() {
return userId.get();
}
}
- Creating a filter class extending OncePerRequestFilter.class, to intercept the details of logged-in user who's already authenticated in the JwtAuthenticationFilter.java
Make sure the created filter executes after the Authentication filter, in this POC it's added in the filter-chain using Spring-security with addAfter()
The primary-id (UUID in this case) is to be stored in the above created class in the ThreadLocal instance, so that only that particular thread can access it (Thread-per-request model is used in a REST-API project) using the defined setter method
if (authentication != null) {
final String authorizationHeader = request.getHeader("Authorization");
final String token = authorizationHeader.substring(7);
final UUID userId = jwtUtils.extractDoctorId(token);
LoggedInDoctorDetailProvider.setUserId(userId);
}
- Taking care of
created_by
andupdated_by
using JPA's@PrePersist
and@PreUpdate
inside the Activity class created in the first step, ThreadLocal instance will provide the primary-id of the authenticated/logged-in user
@PrePersist
void onCreate() {
this.createdBy = LoggedInDoctorDetailProvider.getId();
this.updatedBy = LoggedInDoctorDetailProvider.getId();
}
@PreUpdate
void onUpdate() {
this.updatedBy = LoggedInDoctorDetailProvider.getId();
}
entity-activity-fields-automation-test-recording.mov
- Install Java 17 (recommended to use SdkMan)
sdk install java 17-open
- Install Maven (recommended to use SdkMan)
sdk install maven
- Clone the repo and run the below command in the core
git clone https://github.com/hardikSinghBehl/entity-activity-automator.git
mvn clean install
- To start the application, run any of the below 2 commands in the core
mvn spring-boot:run &
java -jar target/entity-activity-embeder-0.0.1-SNAPSHOT.jar &
- Access the swagger-ui
http://localhost:8080/swagger-ui.html