Quick Flow
runtime composition root는 application start 뒤 새로운 mode/feature scope가 생길 때 object graph를 조립하는 entry입니다. root는 global container가 아니라 Start 가능한 entry와 종료 가능한 scope를 한 번에 만들고 caller에게 넘깁니다.
App root: account, config, telemetry
-> Battle root: battle state, combat system, HUD presenter
-> Start
-> Leave: cancel -> unsubscribe -> release -> dispose
-> Shop root: catalog, pricing, shop presenter| 변화 | root가 할 일 | root 밖에 둘 일 |
|---|---|---|
| mode 진입 | config·scene reference로 graph 조립 | combat/shop 규칙 실행 |
| async asset 준비 | ready·failure·release owner 전달 | UI가 handle을 직접 소유 |
| mode 전환 | old scope를 종료 후 new scope 생성 | child가 global dependency resolve |
| 같은 mode 재시작 | recreate/reset policy 선택 | stale callback을 무시하지 않기 |
graph와 scope를 같이 만들기
CreateBattle은 app-level dependency와 scene-local reference를 받아 battle-specific service, presenter, controller를 연결합니다. child object는 생성 뒤 locator를 다시 찾지 않고 받은 contract만 사용합니다. root가 entry와 Dispose 가능한 scope를 함께 반환하면 caller가 언제 release할지 명확해집니다.
runtime root는 새로운 feature graph가 실제로 달라질 때만 둡니다. interface implementation 하나를 config로 바꾸는 정도면 existing graph에서 strategy를 교체하는 편이 단순합니다. 반대로 shop, battle, editor mode처럼 composition과 lifecycle이 다른 진입점은 각각 root를 두면 app root가 모든 feature detail을 알 필요가 없습니다.
ready, cancel, late callback
Addressables나 remote config를 load하는 graph는 create 직후 ready가 아닙니다. root 또는 scope가 handle을 보관하고, success 뒤 entry를 start하며, failure/cancel에서도 Release합니다. mode를 떠난 뒤 완료된 callback이 old HUD나 old state를 갱신하지 않도록 cancellation token, lifetime version, IsDisposed 같은 guard를 둡니다.
additive scene에서는 active scene 변경, sceneLoaded callback, scene unload가 mode scope와 어떤 순서로 만나는지 정합니다. child graph가 scene object를 참조하면 scope 종료가 scene unload보다 먼저 subscription을 해제하고 handle을 release하도록 흐름을 test합니다.
종료 검증
battle enter → leave → enter를 빠르게 반복하고, loading 중 leave, asset load failure, duplicate entry request를 test합니다. previous scope의 event가 새 scope의 state를 바꾸지 않고, resource handle과 pool lease가 정확히 한 번 release되는지 counter로 확인합니다.
runtime composition root가 service locator로 변하면 새 graph를 조립한다는 이점이 사라집니다. dependency는 진입 시 한 번 연결하고, running object는 필요할 때마다 global resolve하지 않게 하세요.
참고 링크
2 sources