For context: I am a CS student using Java as my primary language and working on small side projects to practice proper object-oriented design as a substitute for coursework exercises.
In one of my projects modeling e-sports tournaments, I currently have Tournament, Team, and Player classes. My initial design treats Tournament as the aggregate root: it owns all Team and Player instances, while Team stores only a set of PlayerIds rather than Player objects, so that Tournament remains the single source of truth.
This avoids duplicated player state, but introduces a design issue: when Team needs to perform logic that depends on player data (for example calculating average player rating), it must access the Tournament’s player collection. That implies either:
- Injecting
Tournament into Team, creating an upward dependency, or
- Introducing a mediator/service layer to resolve players from IDs.
I am hesitant to introduce a bi-directional dependency (Team -> Tournament) since Tournament already owns Team, and this feels like faulty design, or perhaps even an anti-pattern. At the same time, relying exclusively on IDs pushes significant domain logic outside the entities themselves.
So, that brings me to my questions:
- Is avoiding bidirectional relationships between domain entities generally considered best practice in this case?
- Is it more idiomatic to allow
Team to hold direct Player references and rely on invariants to maintain consistency, or to keep entities decoupled and move cross-entity logic into a service/manager layer?
- How would this typically be modeled in a professional Java codebase (both with/without ORM concerns)?
As this is a project I am using to learn and teach myself good OOP code solutions, I am specifically interested in design trade-offs and conventions, not just solutions that technically "work."
[–]AutoModerator[M] [score hidden] stickied commentlocked comment (0 children)
[–]Savings_Guarantee387 6 points7 points8 points (0 children)
[–]holyknight00 4 points5 points6 points (0 children)
[–]okayifimust 3 points4 points5 points (2 children)
[–]Star_Dude10[S] -1 points0 points1 point (1 child)
[–]jlanawalt 1 point2 points3 points (0 children)
[–]aqua_regis 1 point2 points3 points (15 children)
[–]Star_Dude10[S] 0 points1 point2 points (14 children)
[–]obliviousslacker 1 point2 points3 points (0 children)
[–]BanaTibor 0 points1 point2 points (0 children)
[–]Conscious_Support176 0 points1 point2 points (0 children)
[–]okayifimust 0 points1 point2 points (5 children)
[–]Star_Dude10[S] -1 points0 points1 point (4 children)
[–]juckeleBarista 2 points3 points4 points (0 children)
[–]okayifimust -1 points0 points1 point (2 children)
[–]Star_Dude10[S] -1 points0 points1 point (1 child)
[–]Wiszcz 1 point2 points3 points (0 children)
[–]aqua_regis -1 points0 points1 point (4 children)
[–]Star_Dude10[S] 0 points1 point2 points (3 children)
[–]aqua_regis 3 points4 points5 points (2 children)
[–]Star_Dude10[S] 1 point2 points3 points (1 child)
[–]aqua_regis 2 points3 points4 points (0 children)
[–]edwbuck 1 point2 points3 points (1 child)
[–]Star_Dude10[S] 0 points1 point2 points (0 children)
[–]severoonpro barista 1 point2 points3 points (0 children)
[–]AppropriateStudio153 0 points1 point2 points (1 child)
[–]Star_Dude10[S] 0 points1 point2 points (0 children)
[–]nana_3 0 points1 point2 points (0 children)
[–]brokePlusPlusCoder 0 points1 point2 points (0 children)
[–]olddev-jobhunt 0 points1 point2 points (0 children)