Object Identity in Software: Core Principles of Interface, ECS, and Component Design

How separating object identity from representation enables flexible, maintainable software architecture.
This article examines the fundamental software design principle of separating object identity from representation across multiple domains: interface design, Entity-Component-System (ECS) architecture, Domain-Driven Design, and distributed systems. It explores how stable identity enables mutable state, capabilities, and implementations, while also identifying boundary cases where this separation breaks down.
Introduction: Why We Need to Distinguish Identity from Representation
In software engineering, there's a recurring yet often overlooked pattern: an object's state, capabilities, and implementation can all change over time, but its identity remains stable throughout. This observation spans multiple domains including interface design, component systems, ECS (Entity-Component-System), domain entities, and distributed systems.
A developer shared their reflections on this phenomenon on Reddit, raising a thought-provoking question: How should we model "identity" separately from "representation"? And when does this distinction lose its meaning? This article explores this core proposition in depth.
The Nature of Identity: The Tension Between Stability and Mutability
What Is Object Identity
The core of object identity lies in this: it answers "which one is this" rather than "what is it like." Consider an intuitive example—a person from birth to old age undergoes constant changes in appearance, abilities, and health, yet we always consider them "the same person." This "sameness" is independent of any specific attribute representation.
At the programming language level, this distinction is well-established. When comparing two objects, we can ask "are they the same reference" (identity equality) or "do they have the same values" (structural equality). The former concerns identity; the latter concerns representation. Many subtle bugs stem from conflating these two concepts.
Different languages express this distinction in their own ways: In Java, the == operator compares reference identity (whether two variables point to the same memory address), while the .equals() method compares structural equality (whether the objects' attribute values are the same). Python's is vs == distinction works similarly. JavaScript's identity issues are more nuanced due to its prototype chain mechanism—two structurally identical object literals {} and {} evaluate to false under === comparison because they are different references. Rust enforces identity uniqueness at the language level through its ownership system: only one owner can hold a mutable reference at any given time, fundamentally preventing data races caused by identity confusion.
The Mutability of Representation
In contrast to identity's stability, representation is highly mutable:
- State can be updated according to business logic;
- Capabilities can be dynamically added or removed through interface composition;
- Implementation can be entirely replaced during refactoring.
Once we separate identity from representation, the system gains greater freedom to evolve—underlying implementations can be rewritten at will, and as long as the identity contract remains intact, dependents won't be affected.
Interfaces: Defining Identity Boundaries Through Capabilities
Interfaces as Identity Contracts
Interfaces are the most classic manifestation of the identity-representation separation principle. When we program to interfaces, we're essentially declaring: "I care about what this object can do, not what it specifically is." This means the concrete type behind it can be freely substituted, as long as it fulfills the interface contract.
However, there's a trap worth noting here: interfaces define capabilities, not identity. Two different objects implementing the same interface share the same capabilities but are completely different identities. If we carelessly equate "implements the same interface" with "is the same thing," we'll create hidden issues in caching, deduplication, state tracking, and similar scenarios.
Composition and Evolution of Capabilities
Modern software design increasingly favors splitting large interfaces into small, focused capability units, then building complex objects through composition. This approach allows capabilities to evolve independently—an object that only implements a read interface today can add a write interface tomorrow without changing its identity. Identity remains unchanged while the capability set expands. This is the direct application of the "stable identity, mutable representation" principle at the interface level.
This design philosophy has deep theoretical roots. The Interface Segregation Principle (ISP) is one of the SOLID principles, proposed by Robert C. Martin in 1996, with the core assertion that clients should not be forced to depend on methods they don't use. Go takes this idea to its extreme—its interfaces are implicitly implemented (structural typing), meaning a type automatically satisfies an interface simply by having the required method signatures, without explicit declaration. This design makes capability composition extremely flexible and gives "duck typing" compile-time type safety guarantees. Rust's trait system embodies the same philosophy—traits can be freely combined into trait bounds, precisely describing the set of capabilities a generic parameter must possess.
Component Systems and ECS: Complete Decoupling of Identity and Data
The Design Philosophy of ECS
The Entity-Component-System (ECS) architecture pushes the separation of identity and representation to its extreme. In ECS architecture:
- Entities are typically just pure IDs—they are identity itself, carrying no data;
- Components hold all state data;
- Systems operate on these components.
Under this architecture, an entity's "representation" is entirely determined by the set of components currently attached to it, and these can be dynamically added or removed at runtime. A game character entity can gain an "invisibility component" when a spell is cast and have it removed when the spell ends—throughout this entire process, the entity's identity (that ID) never changes.
ECS architecture first gained widespread adoption in game development, with Unity's DOTS (Data-Oriented Technology Stack) and Rust ecosystem's Bevy engine as representative implementations. Unlike traditional object-oriented inheritance hierarchies, ECS completely abandons the "object = data + behavior" encapsulation paradigm, instead thoroughly separating data (components) from behavior (systems), with objects degenerating into pure identifiers. This seemingly radical design choice is precisely the purest expression of the "identity-representation separation" principle.
Engineering Benefits of Data-Oriented Design
The success of ECS demonstrates that "identity-representation separation" is not merely an abstract elegance but delivers tangible engineering benefits: data organized into contiguous component arrays improves cache locality; capabilities can be composed on demand, avoiding the rigidity of inheritance hierarchies. This data-oriented design approach essentially shrinks identity to a minimal stable anchor point and externalizes everything mutable as pluggable components.
To understand these benefits from a hardware perspective: in traditional object-oriented inheritance hierarchies, a GameObject's data is scattered across different locations in heap memory. Iterating over large numbers of objects frequently triggers CPU cache misses, causing severe performance degradation. Modern CPU L1 cache access latency is approximately 1 nanosecond, while main memory access latency is approximately 100 nanoseconds—a hundredfold difference. ECS stores same-type components in contiguous memory arrays (the SoA, Structure of Arrays pattern), allowing systems to fully leverage CPU cache prefetching mechanisms when iterating over a component type, achieving performance improvements of several times to even tens of times. Additionally, independent component storage allows different Systems to operate on disjoint component sets in parallel, naturally suited for multi-threaded concurrent execution. Archetype storage strategies further optimize the memory layout of entities sharing the same component combinations, bringing query performance close to the theoretical limit of linear array scanning.
Domain Entities and Distributed Systems: The Persistence Challenge of Identity
Entities and Value Objects in Domain-Driven Design
In Domain-Driven Design (DDD), the distinction between "Entities" and "Value Objects" is a direct product of the identity question. Entities possess a unique identifier that persists throughout their lifecycle—even if all their attributes change, they remain the same entity; Value Objects, on the other hand, are defined by their attribute values and have no independent identity.
A concrete example: an order is an entity (it has an order number), while the monetary amount within the order is a value object (equality means sameness). This distinction determines how persistence, comparison, and referencing are handled.
In practice, entity unique identifiers are typically generated through three strategies: application-generated (such as UUID v4, based on random numbers with extremely low collision probability), database-generated (such as auto-incrementing primary keys, simple but difficult to coordinate in distributed environments), and generated by bounded context domain logic (such as order numbers composed of date + sequence number, carrying business semantics). Eric Evans specifically emphasizes in his book Domain-Driven Design that entity equality must be judged based on identifiers rather than attribute values—even if two orders have identical amounts, products, and shipping addresses, they are still two different orders. Value Objects follow the opposite semantics: two Money(100, "CNY") instances are completely equivalent and interchangeable. This distinction directly affects persistence strategy—entities require the Repository pattern to manage their lifecycle (create, find, update, delete), while value objects are typically stored as embedded attributes of entities without needing independent database tables.
The Identity Challenge in Distributed Environments
When systems span network boundaries, identity issues become even more challenging. The same logical object may have different replicas across multiple nodes, and these replicas may be temporarily inconsistent due to network latency. At this point, we must rely on stable global identifiers (such as UUIDs) to anchor identity, while delegating eventual state consistency to replication protocols.
Identity stability becomes the cornerstone of distributed system correctness—without it, we cannot even define "these two replicas represent the same object."
In engineering practice, global unique identifier generation schemes each involve tradeoffs: UUID v1 is based on timestamps and MAC addresses, offering ordering but potentially leaking machine information; UUID v4 is based on random numbers, unordered but secure; Twitter's Snowflake algorithm combines timestamps, machine IDs, and sequence numbers, balancing ordering with distributed generation capability. Once identity is established, state consistency becomes the core challenge. The CAP theorem (proposed by Eric Brewer in 2000 and formally proven by Seth Gilbert and Nancy Lynch in 2002) tells us that given the inevitability of network partitions, systems must choose between strong consistency (CP, such as ZooKeeper) and high availability (AP, such as Cassandra). CRDTs (Conflict-free Replicated Data Types) provide an elegant solution: through mathematically provable convergence properties (based on semilattice algebraic structures), they ensure different replicas eventually reach a consistent state without central coordination. Consensus protocols like Raft and Paxos solve the problem from another angle—by electing a Leader to serialize state changes to entities with the same identity, trading strong consistency for reasoning simplicity.
Boundaries: When Identity-Representation Separation Breaks Down
It's worth asking: "identity-representation separation" is not universally applicable. In which scenarios does this distinction stop being useful?
Some typical boundary cases include:
- Pure functional immutable data: When objects are inherently immutable, identity and value often coincide, and deliberately distinguishing them only adds cognitive burden;
- Short-lived temporary objects: If an object is ephemeral, maintaining a stable identity for it serves no practical purpose;
- Scenarios where identity itself changes: Such as entity merging (two accounts merging into one) or splitting—here the "stable identity" assumption breaks down, requiring more complex modeling strategies.
In the purely functional programming paradigm, the identity question has a fundamentally different philosophical interpretation. In languages like Haskell and Clojure, all data structures are immutable—"modifying" an object actually creates a new one. Through Persistent Data Structures—where old and new versions share unmodified portions (structural sharing)—the overhead of full copying is avoided. Clojure's Rich Hickey, in his famous talk "Are We There Yet?", analogized identity to a river—a river's identity lies not in its composition of water molecules at any given instant, but in being a continuous sequence of values over time. Under this paradigm, "identity" is redefined as a timeline of immutable snapshots. This philosophy has profoundly influenced state management design in modern frontend frameworks: Redux models application state as immutable values, with changes expressed by producing new states, while "identity" is implicitly maintained by the store's reference location; React's reconciliation algorithm tracks list element identity through the key attribute—a textbook case of artificially injecting identity information into an immutable virtual DOM tree structure.
Recognizing these boundaries is more important than mechanically applying the principle.
Conclusion
From interface design to ECS architecture, from domain entities to distributed systems, "stable identity, mutable representation" is a deep thread running through software design. It reminds us to ask a fundamental question when modeling: What must remain unchanged, and what can freely evolve?
Shrinking identity to a minimal stable anchor point and externalizing state, capabilities, and implementation as replaceable representations often yields more flexible and maintainable systems. But true design mastery lies in recognizing the boundaries of this principle—knowing when to maintain separation, and knowing when to let identity and representation merge into one.
Key Takeaways
Related articles

cMCP: Adding Signed Receipts to AI Agent Tool Calls for Auditable Denial Mechanisms
cMCP introduces cryptographic signed receipts for AI agent tool call denials under the MCP protocol, enabling auditable refusal credentials for AI governance.

Oxide Computer Raises $445 Million to Rebuild Server Architecture from the Ground Up
Cloud hardware startup Oxide Computer raises $445M to redefine server architecture with open-source firmware and integrated rack-scale design for on-premises cloud experiences.

Media File Organizer: A Free, Open-Source Tool for Automatically Organizing Your Plex Media Library
Media File Organizer is a free, open-source desktop tool that auto-matches TMDB metadata to batch rename and organize movie and TV files into Plex-compatible formats with preview before changes.