"It’s just a helper in the DTO" — Six months later, your DTO is secretly your entire game system.

By Fuat Can Koseoglu,

PR smell: PlayerDTO.EquipItem() now needs three services and even saves itself. • Month 1: Clean PlayerDTO (Username, Level, Health) • Month 2: “Let’s add EquipItem() for convenience.” • Month 3: “DTO should check quest requirements.” • Month 6: PlayerDTO constructor needs IInventoryService, IQuestManager, IDatabase… Now your Data Transfer Object is your equipment system in disguise—classic Active Record drift (a data-only object slowly accumulates behavior and persistence concerns, morphing into a mini application). Why teams slide into this • Performance paranoia: “Fewer objects = faster.” • Unity habit: “One MonoBehaviour owns everything.” • Convenience creep: “Player should equip itself.” • Deadline pressure: “Ship it, refactor later.” The Real Production Cost • Serializer brittleness → Reference loops, drifting saves • Test gridlock → Unit tests boot the whole engine • Heap retention → Event-tied DTOs linger in memory • Multiplayer drift → Live clocks and RNG break determinism • Hot-path perf → Fat DTO copies thrash caches and IL2CPP • AOT/linker pain → Reflection trimming breaks DTO methods • Payload bloat → Save files swell with unnecessary data The Pattern That Scales • DTO: data-only, immutable record class (Username, Level, ItemIds), collections as IReadOnlyList<T> • Domain entity (optional): pure behavior only, no I/O (e.g. TryEquip returns a new state/result) • Application service: orchestrates use cases + I/O (EquipItem, SavePlayer, CheckQuests, repos, clocks, RNG) • Clean separation: testable, serializable, moddable, deterministic PR Litmus (Quick Quality Gates) • Constructor smell: DTO constructors accept only primitives, value objects, or IDs • Serializer probe: Default JSON round-trip passes with no special settings • Determinism probe: Same DTO + same inputs ⇒ same outputs • Unit-test friction: Feature tests run without engine/DI boot • Footprint audit: DTO is lean; hot data split from cold • Golden save: Serialize → deserialize → replay yields the same checksum Key insight: If your PlayerDTO needs services to construct, it’s not a DTO—it’s a confused game system!