Breaking a Circular Dependency and Shipping a Hotfix
Your team owns a 9-module OutSystems 11 Reactive Web application used by 1,400 internal users. The Shared module has grown to 61 public actions and is a producer for every other module. A one-line change there republishes eight modules, and a full Development publish takes 22 minutes. Last sprint a developer needed the Order entity inside Shared, added a reference from Shared to Orders, and the build now fails with a circular dependency; two teams are blocked. LifeTime shows QA is three versions behind Production, and a regulatory hotfix must reach Production tonight. The architect asks you to restore a stable build, define a dependency rule the teams can follow, and get the hotfix out without bypassing QA. Refactoring budget is two sprints, and the business will not accept a feature freeze longer than that.
1. How do you break the Shared and Orders cycle without merging the modules, and what dependency rule prevents it recurring?
Read the worked answer
First, undo the reference that created the cycle. OutSystems compiles an application in dependency order, so a compile-time cycle between two modules in the same application cannot be resolved by build settings; one direction must go.
Choose the cheapest correct inversion. If Shared only needs Order data as a contract, extract a small structure (the fields actually used) into a lower-level Core.Contracts module that both modules reference. Orders keeps the entity and exposes a public action returning the structure. Shared then depends on the contract, not on Orders. Do not move the Order entity itself into Core just to satisfy the compiler: that would give every module write access to a business entity and widen the core rather than narrow it.
If Shared genuinely needs to call Orders at runtime, expose that capability through a Service module or REST/SOAP and consume it. This breaks the compile-time cycle, but you accept network latency, serialization, and deployment ordering between producer and consumer. Use it only for a real behavioural dependency, not for data shapes.
Rule to enforce: Core (entities, structures) -> Foundation (business logic) -> Orchestration and end-user modules (screens, exposed APIs). No module may reference upward, and no UI module may be referenced by anything. Default new actions to Internal, publish only contracts, and review the module reference report each sprint.
2. QA is three versions behind Production and a hotfix is due tonight. Outline the LifeTime promotion path and the risks you accept.
Read the worked answer
The apparent shortcut is the trap. Promoting QA to Production would deploy an older tag and regress the three versions Production already has, so QA cannot be the source of this deployment.
Path: create the fix in Development from the tag currently running in Production (not from Development HEAD, which contains unfinished work), build and tag it. Deploy that same tag to QA, run a smoke test on the fix plus regression on the modules that recompile, then deploy the identical tag to Production inside a maintenance window. QA temporarily receives the hotfix ahead of its own backlog; that is an acceptable, recorded exception. It is not acceptable to deploy untested Development HEAD or to skip QA entirely.
Risks to manage: if the fix touches Shared, eight consumers recompile, so validate the full application. If the entity model changes, the database upgrade script runs and rollback is not simply redeploying the previous tag; avoid destructive schema changes in a hotfix. Keep the previous tag available for application rollback, and plan to bring QA onto the same tag immediately afterwards so the environments do not drift further.
Why not bypass QA: the hotfix is the riskiest deploy of the quarter because it is rushed; QA is the only place the artifact is exercised before users see it.
3. Which of Shared's 61 public actions would you move first, and how do you sequence the refactor across two sprints?
Read the worked answer
Classify before moving. Export the public actions and group them into: true cross-cutting contracts used by three or more modules, actions used by one consumer (candidate for relocation to that consumer), and actions that only look shared because a developer was in a hurry. The third group is usually the largest and carries the least risk.
Sequence: Sprint 1 is low-risk and measurable. Move single-consumer actions into their consumer module, change remaining actions to Internal where no external consumer exists, and extract structures into Core.Contracts. Republish and record the publish time after each change; the goal is fewer recompiled modules per change, not aesthetic purity. Sprint 2 tackles the high-traffic contracts: define the dependency layers formally, split Shared into Core and a Foundation logic module, and migrate consumers in dependency order with a feature flag or parallel action where rollback matters.
Do not attempt a big-bang split of all 61 actions in one sprint. Each move changes the public surface of a producer and forces consumers to recompile, so batch work by dependency layer and keep Production deployable at the end of every week.
Success measure: publish time for a typical change, number of modules recompiled per change, and zero circular dependencies in the reference report. If the business freeze ends after two sprints, the remaining work must be a backlog, not a blocker.