Java Finite State Machine (FSM) Manager
JFSMμ μλ° κΈ°λ°μ κ²½λ μν λ¨Έμ (Finite State Machine) νλ μμν¬μ λλ€. κ°κ²°ν APIλ‘ μν μ²μ΄(transition)λ₯Ό μ μνκ³ , μ½λ°±κ³Ό 쑰건(EventCondition), μ§μ° μ€ν λ° μ¬μλ(Retry) κ°μ μ€λ¬΄ μΉν κΈ°λ₯μ μ 곡ν©λλ€. λ©ν° μ€λ λ κΈ°λ°μ νμ€ν¬ μ€νκΈ°(StateTaskManager)λ₯Ό ν΅ν΄ λΉλκΈ°/μ§μ° μ΄λ²€νΈ μ²λ¦¬λ μ½κ² ꡬμ±ν μ μμ΅λλ€.
- μ΅μ μμ‘΄μ±, μμ μλ°λ‘ λμ
- κ°λ¨ν DSL μ€νμΌμ μν μ²μ΄ λ±λ‘ API
- μ²μ΄ μ±κ³΅/μ€ν¨ μ½λ°±, λ€μ μ΄λ²€νΈ 체μ΄λ(nextEvent)
- μ§μ°(delay) λ° μ¬μλ(nextEventRetryCount) μ§μ
- μ‘°κ±΄λΆ μ€ν(EventCondition) λ° μ€μΌμ€λ§ μ§μ
- λ©ν° μ€λ λ νμ€ν¬ μ²λ¦¬(StateTaskManager)μ μμ ν λμμ± μ μ΄
flowchart TB
subgraph JFSM["JFSM Core"]
SM["StateManager<br/>- FSM μ΅μμ λ§€λμ <br/>- μ€λ λν/νμ€ν¬κ΄λ¦¬<br/>- Handler λ±λ‘/μ‘°ν/μμ "]
SH["StateHandler<br/>- λλ©μΈ κ·Έλ£Ή(μ ν) λ¨μ<br/>- μν/μ΄λ²€νΈ/μ μ΄ λ±λ‘ API μ 곡"]
SEM["StateEventManager<br/>- μ μ΄ ν
μ΄λΈ(Transition) κ΄λ¦¬<br/>- μ΄λ²€νΈ μ²λ¦¬ μ€ν λ‘μ§"]
SU["StateUnit<br/>- μ€μ μνλ₯Ό κ°μ§ μν°ν°<br/>(μΈμ
/μ£Όλ¬Έ/μ₯λΉ λ±)"]
ST["StateTaskManager<br/>- λ©ν° μ€λ λ νμ€ν¬ μ€νκΈ°<br/>- λΉλκΈ°/μ§μ° μ΄λ²€νΈ μ²λ¦¬"]
EC["EventCondition<br/>- μ μ΄ μ€ν μ 쑰건 κ²μ¬"]
CB["CallBack<br/>- μ μ΄ μ±κ³΅/μ€ν¨ νμ²λ¦¬"]
RM["RetryManager<br/>- μ¬μλ μ μ±
/μν κ΄λ¦¬"]
end
SM --> SH
SH --> SEM
SEM --> SU
SEM --> EC
SEM --> CB
SEM --> RM
SM --> ST
ST --> SEM
flowchart TB
IN["μ
λ ₯: (StateUnit, Event)"] --> H["StateHandler<br/>λλ©μΈ κ·Έλ£Ή μ ν"]
H --> EM["StateEventManager<br/>μ μ΄ ν
μ΄λΈ μ‘°ν"]
EM --> COND{"EventCondition<br/>쑰건 ν΅κ³Ό?"}
COND -->|μ| TRANS["Transition μ€ν<br/>StateUnit μν λ³κ²½"]
COND -->|μλμ€| FAILCB["Fail CallBack<br/>μ€ν¨ νμ²λ¦¬"]
TRANS --> SUCCB["Success CallBack<br/>μ±κ³΅ νμ²λ¦¬"]
SUCCB --> NEXT{"nextEvent μ‘΄μ¬?"}
NEXT -->|μλμ€| DONE["μ’
λ£"]
NEXT -->|μ| DELAY{"delay μ€μ ?"}
DELAY -->|μμ| DISPATCH["μ¦μ λ€μ μ΄λ²€νΈ λμ€ν¨μΉ"]
DELAY -->|μμ| SCHED["StateTaskManager<br/>μ§μ° μ€μΌμ€λ§"]
SCHED --> DISPATCH
DISPATCH --> RETRY{"nextEventRetryCount μ€μ ?"}
RETRY -->|μμ| EM2["StateEventManager<br/>λ€μ μ΄λ²€νΈ μ²λ¦¬"]
RETRY -->|μμ| RM["RetryManager<br/>μ¬μλ μν/νμ κ΄λ¦¬"]
RM --> EM2
EM2 --> COND
sequenceDiagram
autonumber
participant C as Client Code
participant SM as StateManager
participant SH as StateHandler
participant EM as StateEventManager
participant EC as EventCondition
participant SU as StateUnit
participant CB as CallBack
participant ST as StateTaskManager
participant RM as RetryManager
C->>SM: addStateHandler("ATM")<br/>νΈλ€λ¬ λ±λ‘
C->>SM: getStateHandler("ATM")<br/>νΈλ€λ¬ μ‘°ν
C->>SH: addState(...)<br/>μ μ΄/μ½λ°±/nextEvent/delay/retry λ±λ‘
C->>SM: addStateUnit(unit)<br/>μν μ λ λ±λ‘
C->>SM: fireEvent(unit, event)<br/>μ΄λ²€νΈ μ€ν(κ°λ
μ )
SM->>SH: λλ©μΈ νΈλ€λ¬λ‘ λΌμ°ν
SH->>EM: μ μ΄ ν
μ΄λΈ μ‘°ν/μ€ν μμ²
EM->>EC: 쑰건 κ²μ¬
alt 쑰건 ν΅κ³Ό
EM->>SU: μν λ³κ²½(Prev/Cur)
EM->>CB: Success CallBack
alt nextEvent μ‘΄μ¬
alt delay μ‘΄μ¬
EM->>ST: μ§μ° μ€ν μ€μΌμ€λ§
ST-->>EM: (μκ° ν) nextEvent μ€ν νΈλ¦¬κ±°
else delay μμ
EM-->>EM: μ¦μ nextEvent μ€ν
end
alt retry μ€μ
EM->>RM: μ¬μλ μν κ°±μ /κ°μ
end
end
else 쑰건 μ€ν¨
EM->>CB: Fail CallBack
end
stateDiagram-v2
[*] --> REGISTERED: StateUnit λ±λ‘
REGISTERED --> READY: μ΄κΈ° μν μ€μ
READY --> RUNNING: Event λ°μ<br/>μ μ΄ ν
μ΄λΈ λ§€μΉ
RUNNING --> READY: Success CallBack<br/>nextEvent μμ
RUNNING --> WAITING: nextEvent + delay
WAITING --> RUNNING: μ§μ° μκ° λλ¬<br/>StateTaskManager μ€ν
RUNNING --> RETRYING: μ€ν¨/쑰건 λΆλ§μ‘±<br/>retry μ€μ
RETRYING --> RUNNING: μ¬μλ(νμ λ¨μ)
RETRYING --> FAILED: μ¬μλ μμ§
FAILED --> [*]
JFSMμ λ€μκ³Ό κ°μ μν©μ μ μ©ν©λλ€.
- μ¬μ©μ/μ 무 νλ¦μ λͺ νν μνλ‘ λͺ¨λΈλ§νκ³ μΆμ κ²½μ°
- μ΄λ²€νΈ κΈ°λ°μΌλ‘ λ¨κ³λ³ μ²λ¦¬μ μλ¬ λ³΅κ΅¬/μ¬μλλ₯Ό νκ³ μΆμ κ²½μ°
- λ©ν° μ€λ λμμ λ€μμ μν κ°μ²΄λ₯Ό μμ νκ² κ΄λ¦¬νκ³ μΆμ κ²½μ°
κ΅¬μ± μμλ λ€μκ³Ό κ°μ΅λλ€.
StateManager: FSM μ 체λ₯Ό κ΄λ¦¬νλ μμ λ§€λμ . μ€λ λ ν ν¬κΈ°, νΈλ€λ¬/μ λ λ±λ‘/μμ λ± μ 곡StateHandler: νΉμ λλ©μΈ(μ ν)μ μ΄λ²€νΈμ μ²μ΄λ₯Ό κ΄λ¦¬StateEventManager: μ²μ΄ ν μ΄λΈκ³Ό μ€ν λ‘μ§μ λ΄λΉStateUnit: μ€μ λ‘ μνλ₯Ό κ°κ³ μ΄λ²€νΈλ₯Ό μ μ©λ°λ κ°μ²΄(μΈμ /μ£Όλ¬Έ/μ₯λΉ λ±)EventCondition: μ²μ΄ μ€ν μ 쑰건 κ²μ¬ μΈν°νμ΄μ€CallBack: μ²μ΄ μ±κ³΅/μ€ν¨ μμ νμ²λ¦¬ μ½λ°± μΈν°νμ΄μ€RetryManager: μ¬μλ μ μ± λ° μν κ΄λ¦¬ μ νΈ
UML κ°μ(μμ):
- FSM:
JFSM/src/main/resources/uml/fsm_uml.puml - ATM λ°λͺ¨:
JFSM/src/test/java/atm/uml/atm_state.puml
νλ μλΉμ€λ μ¬μ©μ νλ¦, μ£Όλ¬ΈΒ·κ²°μ , κΈ°κΈ° μ μ΄(μ: ATM) λ± λ¨κ³μ λ‘μ§μ΄ λ§μ΅λλ€. μ΄ νλ¦μ if-else/switch, νμ΄λ¨Έ, μ€λ λ, ν λ‘μ§μΌλ‘ μ¦ν₯ ꡬννλ©΄ μ½λκ° λΉ λ₯΄κ² 볡μ‘ν΄μ§κ³ κ²°ν©λκ° λμμ§λλ€. FSM(Finite State Machine)μ μμ€ν μ κ°λ₯ν μνμ μ΄λ²€νΈ, μ μ΄λ₯Ό λͺ νν μ μν΄ λ€μκ³Ό κ°μ μ΄μ μ μ 곡ν©λλ€.
- 볡μ‘λ κ΄λ¦¬μ κ°λ
μ± ν₯μ
- μνμ μ μ΄(transition)λ₯Ό νλ‘ κ΄λ¦¬νλ―λ‘ νλ¦μ΄ λͺ νν©λλ€.
- "μ΄λ€ μ΄λ²€νΈκ° μ΄λ€ μνμμ μ ν¨νκ°"λ₯Ό μ½λ λ 벨μμ κ°μ ν μ μμ΅λλ€.
- μ λ’°μ± μλ μ€λ₯ μ²λ¦¬μ ν볡λ ₯
- μ±κ³΅/μ€ν¨ μ½λ°±μΌλ‘ νμ²λ¦¬λ₯Ό μΌκ΄λκ² μνν©λλ€.
nextEvent+delay+nextEventRetryCountλ‘ μ€ν¨ ν μ¬μλΒ·λ°±μ€νΒ·λκΈ° κ°μ ν볡 μ λ΅μ μ μΈμ μΌλ‘ ꡬμ±ν μ μμ΅λλ€.
- λμμ± μ μ΄μ λΉλκΈ° μ²λ¦¬μ λ¨μν
StateTaskManagerκ° μ§μ° μ€νκ³Ό 체μ΄λμ μ€λ λ νμμ μμ νκ² μ²λ¦¬ν©λλ€.- μ΄λ²€νΈ 쑰건(
EventCondition)μΌλ‘ κ²½μ 쑰건μ΄λ μ ν¨μ± μ κ²μ¬λ₯Ό μ€μνν©λλ€.
- ν
μ€νΈ μ©μ΄μ±κ³Ό μΆμ κ°λ₯μ±
- κ° μ μ΄μ μ½λ°±μ΄ λ 립 λͺ¨λμ΄λ―λ‘ λ¨μ ν μ€νΈκ° μ½μ΅λλ€.
- μν μ΄λ ₯(Prev/Cur)μΌλ‘ μΌμ΄μ€ μ¬νκ³Ό μ₯μ λΆμμ΄ μ©μ΄ν©λλ€.
- λλ©μΈ μ ν©μ±κ³Ό νμ₯μ±
- λλ©μΈ μΈμ΄(μ: READY β CARD_READ β PIN_ENTRY)λ₯Ό κ·Έλλ‘ μ½λμ λ°μν΄ μμ¬μν΅μ΄ μ¬μμ§λλ€.
- μλ‘μ΄ μν/μ΄λ²€νΈ μΆκ°κ° κΈ°μ‘΄ μ½λμ μ΅μνμ μν₯μΌλ‘ κ°λ₯ν©λλ€.
JFSMμ΄ μ 곡νλ κΈ°λ₯μ FSM λμ ν¨κ³Όλ₯Ό μ€λ¬΄μ λ§κ² κ°νν©λλ€.
- μ μΈμ API:
StateHandler.addState(...)λ‘ μ μ΄, μ½λ°±, λ€μ μ΄λ²€νΈ, μ§μ°, μ¬μλκΉμ§ ν λ²μ μ μ - μ‘°κ±΄λΆ μ€ν:
EventConditionμΌλ‘ μ¬μ κ²μ¦ λ‘μ§μ νλ¬κ·ΈμΈμ²λΌ μΆκ° - λΉλκΈ°Β·μ€μΌμ€λ§: λ΄λΆ μ€μΌμ€λ¬/μ€λ λν κΈ°λ°μΌλ‘ λ€μ μ΄λ²€νΈ μλ μ²λ¦¬
- μ¬μλ μ λ΅:
RetryManagerλ₯Ό ν΅ν΄ λ¨μ λ°λ³΅λΆν° μ ν νμΒ·μ§μ° κ°κ²©κΉμ§ μ μ°νκ² κ΅¬μ±
μΈμ FSMμ μ°μ§ μμλ λλκ°?
- μ μ΄κ° 1~2κ° μμ€μ μμ£Ό λ¨μν νλ‘μ°
- μν κ°λ μ΄ κ±°μ μκ³ μ μ°¨ν μ§μ λ‘μ§μΌλ‘ μΆ©λΆν κ²½μ° μ΄λ° κ²½μ°μλ μ€νλ € FSMμ΄ κ³Όμ€κ³κ° λ μ μμ΅λλ€. κ·Έλ¬λ μν/μ΄λ²€νΈκ° λμ΄λκ³ λΉλκΈ°Β·μ§μ°Β·μ¬μλκ° μ½νλ©΄ FSMμ΄ μ₯κΈ° μ μ§λ³΄μ κ΄μ μμ μ 리ν©λλ€.
μλλ μ΅μ κ΅¬μ± μμμ λλ€.
import com.fsm.StateManager;
import com.fsm.module.StateHandler;
import com.fsm.event.base.CallBack;
// 1) FSM λ§€λμ μμ± (νμ€ν¬ μ€λ λ μ΅λ κ°μ μ§μ )
StateManager fsm = new StateManager(4);
// 2) νΈλ€λ¬ λ±λ‘ (λλ©μΈ κ·Έλ£Ήλͺ
)
fsm.addStateHandler("ATM");
StateHandler handler = fsm.getStateHandler("ATM");
// 3) μν μ λ λ±λ‘ (μ€μ μνλ₯Ό κ°μ§ κ°μ²΄)
fsm.addStateUnit("acct-001", "ATM", "INIT", new Object());
// 4) μ²μ΄ μ μ (event, from, to, μ±κ³΅μ½λ°±, μ€ν¨μ½λ°±, λ€μμ΄λ²€νΈ, μ§μ°(ms), μ¬μλ)
CallBack ok = (unit, params) -> {
System.out.println("on success: " + unit.getName());
return true;
};
CallBack fail = (unit, params) -> {
System.out.println("on fail: " + unit.getName());
return true;
};
handler.addState(
"INPUT_PIN", // event
"INIT", // from
"PIN_ENTERED", // to
ok, // success callback
fail, // fail callback
"SELECT_MENU", // next event (체μ΄λ)
0, // delay (ms)
0 // retry count
);
// 5) μ΄λ²€νΈ μ€ν
var unit = fsm.getStateUnit("acct-001");
String nextState = handler.fire("INPUT_PIN", unit);
System.out.println("Next State: " + nextState);
// μ’
λ£ μ
fsm.stop();- StateManager
- FSM μ 체 μλͺ κ³Ό 리μμ€λ₯Ό κ΄λ¦¬ν©λλ€.
- νΈλ€λ¬(
StateHandler)μ μ λ(StateUnit)μ λ±λ‘/μμ ν©λλ€. - λ΄λΆμ
StateTaskManagerλ₯Ό 보μ νλ©°, λΉλκΈ° μμ μ μ€νν©λλ€.
- StateHandler
- μ΄λ²€νΈ μ²μ΄ μ μλ₯Ό λ±λ‘νκ³ ,
fire/handleλ‘ μ€νν©λλ€. - λμΌ νΈλ€λ¬ λ΄ μ΄λ²€νΈ κ° μ²΄μ΄λ(nextEvent)κ³Ό μ§μ°μ μ²λ¦¬ν μ μμ΅λλ€.
- μ΄λ²€νΈ μ²μ΄ μ μλ₯Ό λ±λ‘νκ³ ,
- StateEventManager
- μ²μ΄ ν μ΄λΈμ μ μ₯νκ³ μ€ν λ‘μ§μ μνν©λλ€.
- μ±κ³΅/μ€ν¨ μ½λ°±, μ¬μλ, 쑰건 κ²μ¬ λ±μ μ€μΌμ€νΈλ μ΄μ ν©λλ€.
- StateUnit
- μ΄λ¦, νμ¬ μν, λΆκ° λ°μ΄ν°(payload)λ₯Ό κ°λ FSMμ λμ κ°μ²΄μ λλ€.
- EventCondition
- νΉμ μ΄λ²€νΈ μ€ν μ 쑰건μ κ²μ¦ν©λλ€.
- μ€ν¨/μ€λ₯ μν© μ λ€μ μ²μ΄λ μ½λ°± λμμ μν₯μ μ€ μ μμ΅λλ€.
- CallBack
- μ²μ΄ μ±κ³΅/μ€ν¨ μμ μ νμ²λ¦¬λ₯Ό ꡬνν©λλ€.
- Retry(μ¬μλ)
nextEventRetryCountλ‘ λ€μ μ΄λ²€νΈ 체μ΄λ μ μ¬μλ νμλ₯Ό μ§μ ν©λλ€.
StateManager fsm = new StateManager(4); // μ€λ λ μ΅λ 4κ°fsm.addStateHandler("ORDER");
StateHandler handler = fsm.getStateHandler("ORDER");fsm.addStateUnit("order-1001", "ORDER", "NEW", payload);
fsm.getStateUnit("order-1001");
fsm.removeStateUnit("order-1001");μ€λ²λ‘λ λ κ°μ§κ° μ 곡λ©λλ€.
- λ¨μΌ from μνμμ toλ‘ μ²μ΄
- λ€μ€ from μν μ§ν©μμ toλ‘ μ²μ΄
boolean added = handler.addState(
"PAY", // event
"NEW", // fromState
"PAID", // toState
successCb, failCb,
null, // nextEvent μμΌλ©΄ null
0, // delay
0, // retry count
new Object[]{/* next params */}
);HashSet<String> from = new HashSet<>(List.of("NEW", "RETRY"));
boolean added = handler.addState(
"PAY", // event
from, // fromStateSet
"PAID", // toState
successCb, failCb,
"SHIP", // nextEvent
1000, // delay 1s ν nextEvent
3, // μ΅λ 3ν μ¬μλ
"param1", 123
);String nextState = handler.fire("PAY", stateUnit);addStateμnextEvent,delayλ₯Ό μ§μ νλ©΄ μ±κ³΅ μ λ€μ μ΄λ²€νΈλ₯Ό μ§μ° ν μλ μ€νν©λλ€.nextEventRetryCountλ‘ μ€ν¨ μ μ¬μλ νμλ₯Ό μ μ΄ν μ μμ΅λλ€.
EventConditionμ νΈλ€λ¬μ λ±λ‘ν΄ μ΄λ²€νΈ μ€ν μ κ²μ¦μ μνν μ μμ΅λλ€.
handler.addEventCondition((event, unit) -> {
// μ€ν κ°λ₯ μ¬λΆ κ²μ¬ (true=ν΅κ³Ό)
return unit != null && unit.getIsAlive();
}, 0); // delay(ms) β νμ μ 쑰건λ μ§μ° νκ° κ°λ₯StateTaskManagerμ λ΄λΆ μ€μΌμ€λ¬κ° μ§μ°/체μ΄λ μμ μ λ°±κ·ΈλΌμ΄λμμ μ€νν©λλ€.StateManagerμμ±μμ νλΌλ―Έν°λ‘ μ€λ λ μ΅λ κ°μλ₯Ό μ€μ ν©λλ€.
- μ€ν¨ μ½λ°±μμ λ‘κΉ /μλ¦Ό/보μ λ‘μ§μ μνν μ μμ΅λλ€.
nextEventRetryCountμRetryManagerλ₯Ό ν΅ν΄ λ°λ³΅ μ€ν μ λ΅μ μ€κ³νμΈμ.
ν μ€νΈ μ½λμ ATM μν λ¨Έμ μμ κ° ν¬ν¨λμ΄ μμ΅λλ€.
- κ²½λ‘:
JFSM/src/test/java/atm - UML:
JFSM/src/test/java/atm/uml/atm_state.puml
μ€ν ν:
# Maven ν
μ€νΈ μ€ν
mvn -f JFSM/pom.xml test -Dtest=atm.BasicAtmStateTest# μ 체 ν
μ€νΈ
mvn -f JFSM/pom.xml test
# νΉμ ν
μ€νΈ ν΄λμ€
mvn -f JFSM/pom.xml -Dtest=TestMain testνλ‘μ νΈλ Maven κΈ°λ°μ
λλ€. λ‘컬 λͺ¨λλ‘ μ¬μ©ν κ²½μ° StateManager λ± APIλ₯Ό μ§μ import νμ¬ μ¬μ©νμΈμ. (λ³λμ μ€μ μ μ₯μ λ°°ν¬ μ λ³΄κ° μλ€λ©΄ μμ€ μμ‘΄ λλ λ‘컬 μ€μΉλ₯Ό μ΄μ©νμΈμ.)
# λΉλ
mvn -f JFSM/pom.xml clean package
# λ‘컬 μ€μΉ (λ€λ₯Έ νλ‘μ νΈμμ μ¬μ©)
mvn -f JFSM/pom.xml clean installμμ‘΄ μμ(pom.xml) β λ‘컬 μ€μΉ ν μ¬μ© μ:
<dependency>
<groupId>com.fsm</groupId>
<artifactId>JFSM</artifactId>
<version>1.0.0</version>
</dependency>- Q. μν μ²μ΄λ μ΄λμ μ μνλμ?
- A.
StateHandler.addState(...)λ₯Ό ν΅ν΄ μ μν©λλ€.
- A.
- Q. μ¬λ¬ from μνμμ κ°μ μ΄λ²€νΈλ‘ μ²μ΄ κ°λ₯ν κΉμ?
- A. κ°λ₯ν©λλ€.
HashSet<String>μΌλ‘ from μν μ§ν©μ λκΈ°λ©΄ λ©λλ€.
- A. κ°λ₯ν©λλ€.
- Q. λ€μ μ΄λ²€νΈλ₯Ό μλ μ€ννκ³ μΆμ΅λλ€.
- A.
nextEventμdelayλ₯Ό μ§μ νμΈμ. μ€ν¨ μnextEventRetryCountλ‘ μ¬μλ νμλ₯Ό μ€μ ν©λλ€.
- A.
- Q. μ€λ λ κ°μλ μ΄λ»κ² μ‘°μ νλμ?
- A.
new StateManager(threadMaxCount)λ‘ μμ± μ μ€μ ν©λλ€.
- A.
μ΄ νλ‘μ νΈλ LICENSE νμΌμ λ΄μ©μ λ°λ¦
λλ€.
