Implementing State management for a Spring boot application - spring-boot

I am working on a Spring boot application, where I need to implement a state transition table in the following format, where the combination of action and condition determines the transition of the entity from one state to another state:
From State| To State| User Action | Condition
----------------------------------------------------------------
S0 | S1 | Create a record |
-----------------------------------------------------------------
S1 | S1 | Update a record |
-----------------------------------------------------------------
S1 | S1 | Run validation |
-----------------------------------------------------------------
S1 | S2 | Mark state as S2 | Record is valid
In this application, there are already APIs for Create, Update, and Running Validation.
As per the requirement, the state machine should be configurable, so that any new state can be added/existing state can be deleted as well as action and condition can be changed for a given transition through the dashboard.
To accomodate this requirement, I have thought of considering State, Action, Condition and StateTransition as separate entities as follows:
Action entity:
#Entity
#Table(name = "action")
public class Action implements Serializable {
#Id
private String id;
#Column(name = "action_type")
#Enumerated(EnumType.STRING)
private ActionType actionType;
#Column(name = "action_description")
private String actionDescription;
}
Action type enum:
Enum ActionType {
CREATE_RECORD, UPDATE_RECORD, RUN_VALIDATION;
}
State entity:
#Entity
#Table(name = "state")
public class State implements Serializable {
#Id
private String id;
#Column(name = "name")
private String name;
}
Condition entity (Cannot understand how to do this)
#Entity
#Table(name = "condition")
public class Condition implements Serializable {
#Id
private String id;
#Column(name = "description")
private String description;
... CANNOT UNDERSTAND WHAT ELSE SHOULD I PUT HERE
}
State Transition entity as follows:
#Entity
#Table(name = "state_transition")
public class State implements Serializable {
#Id
private String id;
#Column(name = "from_state")
private String fromStateId;
#Column(name = "to_state")
private String toStateId;
#Column(name = "action_id")
private String actionId;
#Column(name = "condition_id")
private String conditionId;
}
Basically, with the combination of current state, action, and condition, I should be able to figure out the next state from the state transition table.
But I am stuck in the following things:
How should I implement the User Action? As I mentioned before, some of these actions as I specified above have been there in the application as APIs. Basically, I cannot understand how can I tie the user action entity that I would like to implement with the existing flow.
How should I implement the Condition as an entity ? Condition is basically an expression that either evaluates to True/False. I am not getting any clue as, how can I capture this as an entity.
Could anyone please give any pointer regarding this? Thanks.

I don't see a reason for introducing all these entities. Action seems to be static i.e. you are defining the possible actions in code, so why bother and create an entity for that? Condition is just a field in the Transition entity containing an expression. I also don't think you need to have a dedicated entity for State, but that might be debatable. Overall, I would suggest you serialize the state machine as JSON though because you will always read the entire state machine anyway. So unless you want to be able to query or refer to dedicated elements in that definition (referential integrity), don't model this as tables. So either
#Entity
#Table(name = "state_transition")
public class StateTransition implements Serializable {
#Id
private String id;
#Column(name = "from_state")
private String fromState;
#Column(name = "to_state")
private String toState;
#Column(name = "action")
private ActionType action;
#Column(name = "condition")
private String condition;
}
or
#Entity
#Table(name = "state_machine")
public class StateMachine implements Serializable {
#Id
private String id;
#Column(name = "definition")
private String definition;
}
You can then de-serialize the definition or even do that in an AttributeConverter to whatever model that suits your needs.

Related

JPARepository CPRQ modified does not save full object

I have modified the design of CPRQ a bit to help my database pattern
I have an Employee table and a Department table. Both have common properties
#Column(name="tenantIDPKFK")
private Integer tenantIdpkfk;
#Column(name="status")
private Integer status;
So I created a base class ABaseEntity like below
public class ABaseEntity {
public ABaseEntity() {
}
public ABaseEntity(int tenantIdpkfk, int status) {
this.tenantIdpkfk = tenantIdpkfk ;
this.status = status ;
}
#Column(name="tenantIDPKFK")
private Integer tenantIdpkfk;
#Column(name="status")
private Integer status;
I have extended EmployeeEntity with ABaseEntity
#Entity
#Table(name = "employee")
public class EmployeeEntity extends ABaseEntity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "first_name")
#NotEmpty(message = "Please provide a name")
#NotBlank
private String firstName;
My CommandHandler runs the following code
EmployeeEntity savedEmployeeEntity = this.employeeRepository.saveAndFlush(employee);
this.mediator.emit(new EmployeeCreatedEvent(savedEmployeeEntity.getId()));
Database saved the object, but only id, firstname. Does not save tenant and status columns.
I know I am missing something silly. Please help.
EDIT
Adding #MappedSuperclass to the ABaseEntity class fixed the issue.
#MappedSuperclass
public class ABaseEntity {...}
Database saved the object, but only id, firstname. Does not save
tenant and status columns.
By default JPA doesn't consider the parent class in the orm (object-relational mapping) of the current class.
You have to specify on the parent class #Inheritance with the strategy to use or use the default one.
For example :
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ABaseEntity {...}
More info here.

Spring JPA bidirectional relation on multiple nested entities

I know there has been multiple questions on bidirectional relations using spring jpa in the past but my case is a little bit different because i am using 3 entities with 2 relationships to implement a medical system
I have 3 entities : doctor/patient/appointment
here is the code for the 3 entities
please note all setters , getters and constructors implemented but ommited here for clarity
Patient class
#Entity
public class resPatient {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String name;
private String gender;
private String email;
private String mobile;
private int age;
private String notes;
#OneToMany(mappedBy = "patient")
List<resPackageMembership> memberships;
#OneToMany(mappedBy = "patient")
List<resAppointment> appointments;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "patient")
List<resMedImage> medImages;
Doctor class
#Entity
public class resDoctor {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String name;
private String mobile;
private String email;
private String gender;
private int age;
private String speciality;
#OneToMany(mappedBy = "doctor")
List<resAppointment> appointments;
Appointment class
#Entity
public class resAppointment {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String speciality;
#Basic
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated;
#Basic
#Temporal(TemporalType.TIMESTAMP)
private Date dateToVisit;
private String status;
private String notes;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctorCode")
private resDoctor doctor;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "patientCode")
private resPatient patient;
the way my medical system should work is that when i get a patient using my restful controller i want all the patient data including his appointments but this leads to an infinite loop as the appointment has the doctor which also has appointments and so on.
i cannot user #JSONIGNORE as there are 2 relationships i want to get the patient with his appointments which should have the doctor without the appointments array and should not have any patient data as i already am in the patient object
As a general best-practice, it's recommended to separate the entities from the data transfer objects used for the rest controllers. With DTO's in place, you have more control on which data to include and serialize within them to avoid the circlular references.
If you like check out https://bootify.io, it generates the DTOs from your database schema, but the custom endpoint you still need to define/build.
I develop an annotation processor called beanknife recently, it support generate DTO from any class. You need config by annotation. But you don't need change the original class. This library support configuring on a separate class. Of course you can choose which property you want and which you not need. And you can add new property by the static method in the config class. For your question:
// this will generate a DTO class named "resPatientView".
// You can change this name using genName attribute.
#ViewOf(value=resPatient.class, includePattern = ".*")
public class PatientViewConfigure {
// here tell the processor to automatically convert the property appointments from List<resAppointment> to List<resAppointmentWithoutPatient>.
// resAppointmentWithoutPatient is the generated class configured at the following.
// Note, although at this moment it not exists and your idea think it is an error.
// this code really can be compiled, and after compiled, all will ok.
#OverrideViewProperty("appointments")
private List<resAppointmentWithoutPatient> appointments;
}
// here generated a class named resAppointmentWithoutPatient whick has all properties of resAppointment except patient
#ViewOf(value=resAppointment.class, genName="resAppointmentWithoutPatient", includePattern = ".*", excludes={"patient"})
public class AppointmentWithoutPatientViewConfigure {
// the doctor property will be converted to its dto version which defined by the configure class DoctorWithoutAppointmentsViewConfigure.
#OverrideViewProperty("doctor")
private resDoctorWithoutAppointments doctor;
}
// here we generate a class which has all properties of resDoctor except appointments
#ViewOf(value=resDoctor.class, genName="resDoctorWithoutAppointments", includePattern = ".*", excludes={"appointments"})
public class DoctorWithoutAppointmentsViewConfigure {}
// in you rest controller. return the dto instead of the entities.
resPatient patient = ...
resPatientView dto = resPatientView.read(patient);
List<resPatient> patients = ...
List<resPatientView> dto = resPatientView.read(patients);
At the end, the class resPatientView will has the same shap with resPatient except its appointments not having patient property and its doctor property is replaced with a version without appointments property.
Here are more examples.
The version 1.10 is ready. Will fix some bug and support the configure bean to be managed by spring.

How to cascade delete unidirectional

I have a case where I have a user and the user had an EmailVerificationToken.
I would like to delete the EmailVerificationToken when the user gets deleted.
However, since the EmailVerificationToken is an object that is only needed for a short period of time (ie only used once and is irrelevant after), I don't want the User entity to contain the token. Instead, I want the EmailVerificationToken to reference the user it belongs to, but not the other way around.
How do I set it up so when I delete the user, it deletes the EmailToken even though it doesn't reference it in the User entity?
This is the code I have currently:
public class EmailVerificationToken implements IEntity, IDto {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "emailVerificationTokenId")
private Long id;
#OneToOne
#JoinColumn(name = "userId", nullable = false)
private User user;
}
and
public class User implements IEntity, IDto, UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "userId")
private Long id;
}
I am guessing you have a transactional service which handles the deletion of a User.
You need to add a named query in your EmailVerificationToken class. Something like
#NamedQuery(name = EmailVerificationToken.FIND_BY_USER, query = "Select e from EmailVerificationToken e where e.user =:user"),
while adding a constant in your class for the name of the query, like:
public static final String FIND_BY_USER = "EmailVerificationToken.FindByUser";
Then you need to define a service which finds a managed instance of your token class with the given User instance.
Then in the transactional method, where you would delete the user, first delete the token;
public void deleteUser(User user){
EmailVerificationToken token = someService.findByUser(user); //you get a
//managed instance using the previously defined query
em.remove(token);
em.remove(user);
}
em is an instance of the Entity manager.
Hopefully this helps you. For any further questions, you are free to ask.

Fetch specific property in hibernate One-to-many relationship

I have two pojo classes with one-to-many relationship in hibernate
CustomerAccountEnduserOrderDetails.class
#Entity #Table(name="customer_account_enduser_order_details")
public class CustomerAccountEnduserOrderDetails implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "product_id", insertable = false, updatable = false)
private CustomerCmsProduct customerCmsProduct;
}
Second is CustomerCmsProduct.class
#Entity
#Table(name="customer_cms_product")
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerCmsProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="name")
private String name;
#Column(name="offer_price")
private String offerPrice;
#Column(name="original_price")
private String originalPrice;
#Column(name="discount")
private String discount;
}
Here if I fetch the object of CustomerAccountEnduserOrderDetails class,then i will get the CustomerCmsProduct class also , my problem is that here i want the specific column of CustomerCmsProduct table (not all by default i am getting all) like only id and originalPrice.
How i can do like that projection here?
In the service layer or at a webservice layer( if this is a web project) Create two different classes other than #Entity as DTO(Data Transfer Objects) which helps is data transfer from one layer to the other.
public class CustomerAccountEnduserOrderDetailsPojo {
private List<CustomerCmsProductPojo> productPojoList = new ArrayList<> ();
// getter and setter
}
public class CustomerCmsProductPojo {}
Follow the below steps
Retrieve the #Entity class data by executing the query from service layer.
Iterate over all the fields and copy only required fields to pojo layer
Expose the data to other layers using service method.
This way, we can avoid changing the custom hibernate behavior as it is linked with many parameters like cache, one to many queries that are fired per iteration.
And also, do any customization that you want in this layer. Hope this is multi layered project where you have different layers which servers different purpose.

Spring Data JPA inserting instead of Update

Hi I am new to Spring Data JPA and I am wondering even though I pass the Id to the entity, the Spring data jpa is inserting instead of merge. I thought when I implement the Persistable interface and implement the two methods:
public Long getId();
public Boolean isNew();
It will automatically merge instead of persist.
I have an entity class called User like:
#Entity
#Table(name = "T_USER")
public class User implements Serializable, Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "USER_ID")
private Long id;
#Column(name = "CREATION_TIME", nullable = false)
private Date creationTime;
#Column(name = "FIRST_NAME", nullable = false)
private String firstName;
#Column(name = "LAST_NAME", nullable = false)
private String lastName;
#Column(name = "MODIFICATION_TIME", nullable = false)
private Date modificationTime;
And have another class
#Entity
#Table(name = "T_USER_ROLE")
public class UserRole implements Serializable, Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long roleId;
#Column(name = "ROLE_NAME")
private String userRole;
}
I have a custom repository called UserRepostory extending JpaReopistory. I am hitting the save for merge and persist as I see the implementation demonstrate that Spring Data Jpa uses above two methods to either update or insert.
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
I have been trying to figure out but didn't get any clue. Maybe you
guys can help.
I ran into this issue, tried to implement Persistable to no avail, and then looked into the Spring Data JPA source. I don't necessarily see this in your example code, but I have a #Version field in my entity. If there is a #Version field Spring Data will test that value to determine if the entity is new or not. If the #Version field is not a primitive and is null then the entity is considered new.
This threw me for a long time in my tests because I was not setting the version field in my representation but only on the persisted entity. I also don't see this documented in the otherwise helpful Spring Data docs (which is another issue...).
Hope that helps someone!
By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity will be assumed as new, otherwise as not new. It's Id-Property inspection Reference
If you are using Spring JPA with EntityManager calling .merge() will update your entity and .persist() will insert.
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public User save(User user) {
if (user.getId() == null) {
em.persist(user);
return user;
} else {
return em.merge(user);
}
}
There is no need to implement the Persistable interface.

Resources