I have n classes that extends from a State class, the purpose of the State class is to manage the state of the extended classes. For each class we need to save it in the database, remove it and select all the data in order to recover the classes to the same timestamp we stopped (in case of system failure or shutdown).
For example:
We have TransactionEntry that extends from State, each time we process some transaction, we insert it to the DB and when the processing is over, we remove it. In case our system suddenly shouted down, we need to recover all the Transaction objects (which did not processed successfully) from the DB to our Transaction list in the system in order to keep processing them.
Each Entry has a different way to recover (the way we select the data from the DB in order to add it to the list in the system) and some of them has the ability to update data in the DB.
For example:
FeedbackEntry can do all the things TransactionEntry can do but recover the data from the DB by State and update the current feedback state.
Feedbacks has state, like running or delayed so we want the ability to recover all the running (or delayed) feedbacks. Because feedback has state, and the state is changing sometimes (during the processing time), we need the ability to update the state in the DB.
So FeedbackEntry can do all the things TransactionEntry can do but the way we recover the data is different and because feedback has the ability to change state, we need to allow update query.
So far we discovered that we need to insert, remove and recover (by select) to our entries. The difference is in the way we recover them and the abilities that comes with each way.
Moreover, we want to add more abilities to some Entries. Some of them need the ability to Retry in case of Exception or in case something went wrong.
For example:
OverrideEntry can do all the things FeedbackEntry can do (which is recover by state, update state, insert, select, remove) and the ability to update retry count. OverrideEntry has some logic, that in case of Exception we must keep try to process for 3 / 4 times. So for each retry time, we have to update the retry counter in the DB.
As i understand, OverrideEntry needs the ability to insert, remove, select, recover by state, update state and update retry count.
How do i see the implementation ?
I see an abstract class called State:
public abstract class State<T extends StateEntry> implements Stateable<T> {
// Some Connection object to the database
@Override
public void insert(T entry) {
// insert entry to DB
}
@Override
public void remove(T entry) {
// delete entry from the DB
}
@Override
public List<T> select(String query) {
// select all the data by query
// (this query is flexible in order to serve the recovery types)
return null;
}
}
Stateable interface:
public interface Stateable<T extends StateEntry> {
void insert(T entry);
void remove(T entry);
List<T> select(String query);
}
They are exposing the base abilities of insert, select and remove.
Now, each entry will implement a Recoverable or RecoverableByState:
public interface Recoverable<T extends StateEntry> extends Stateable<T> {
void recover(Addable<T> manager);
}
public interface RecoverableByState<T extends StateEntry> extends Stateable<T> {
void recover(Addable<T> manager, int state);
void updateState(T entry, int state);
}
Addable is an interface that allow us to add the recovered data to our list:
public interface Addable<T extends StateEntry> {
void add(T entry);
}
All the logic on the entries is in Manager classes. Each manager manages the logic of the entry, for example:
public class TransactionManager implements Addable<TransactionEntry> {
private ArrayList<TransactionEntry> transaction;
void checkTransaction(TransactionEntry entry){
// ... logic
// In other entries, their manager can change things on the entry
// that we must record this change in the DB, like state (from delayed to running)
// or update retry count or something like that, so for some entries
// we need the ability to update the fields we need:
// overrideState.updateRetryCount(overrideEntry, 3); // will update the retry count of overrideEntry to 3
// or overrideState.recover(new OverrideManager(), State.running)
}
void closeTransaction(TransactionEntry entry) {
// Remove from state
// state.remove(entry)
transaction.remove(entry);
}
@Override
public void add(TransactionEntry entry) {
// Insert entry to state
// state.insert(entry)
transaction.add(entry);
}
}
Now, for each entry that needs to implement more abilities, we will create and implement an interface like:
public interface Retryable<T extends StateEntry> {
Connection postgresConnection();
default void updateRetryCount(T entry, int retryCount) {
String query = "update entries_state set retry = {0} where id = {1}";
{0} -> retryCount
{1} -> entry.getID()
this.postgresConnection.execute(query);
}
}
Example of entries:
A base entry, contains all the common fields of the entries:
public abstract class StateEntry {
// Will have all the common fields of the entries
// like guid, record_creation_time, entry_name...
}
On TransactionEntry we need simple recover without special abilities
public class TransactionEntry extends StateEntry {
@JsonProperty("TransactionID")
private String transactionId;
@JsonProperty("NumOfEntitiesOnTransaction")
private int entitiesOnTransaction;
@JsonProperty("CreationTime")
private Instant creationTime;
// Ctor
}
On FeedbackEntry we need to recover by state and also update the state
public class FeedbackEntry extends StateEntry {
@JsonProperty("Host")
private String host;
@JsonProperty("Feedback")
private JsonNode feedback;
@JsonProperty("SendingTime")
private Instant sendingTime;
@JsonProperty("StateType")
private StateType stateType;
// Ctor
}
On OverrideEntry we need to recover by state and
public class OverrideEntry extends StateEntry {
@JsonProperty("Message")
private String message;
@JsonProperty("StartProcessingTime")
private Instant startProcessingTime;
@JsonProperty("RetryCount")
private int retryCount;
@JsonProperty("StateType")
private StateType stateType;
// Ctor
}
How it looks in the Database ?
I see a single table, called entries_state which each field in StateEntry (the base class that all the entries extends from) is a column and one more column of type Json that contains the entry itself after serialization from Object to Json.
My question
Does it sound and looks good for you ?.
I want to be able to create states easily as:
Stateable<TransactionEntry> transactionState = StateFactory.createInstance(StateType.Recover);
Stateable<FeedbackEntry> feedbackState = StateFactory.createInstance(StateType.RecoveryByState);
Stateable<OverrideEntry> overrideState = StateFactory.createInstance(StateType.RecoveryByState, StateType.Retry);
I mean i want to create some Factory that will create the wanted combination of state abilities as i pass. I do not think this is possible in the way i want to implement so i will glad to hear your opinions, maybe all the way i think is wrong. I'm thinking about a solution for 3 days and can't get a good one.
Thank you all for your time and your helping.
Ben.