Building the Backbone: Entities Part 1, Document

Michael Flanagan - Nov 2 - - Dev Community

🏗️ Laying the Foundation: Embracing Worldbuilding and Game Mastery

In my previous post, I embarked on an AI journey to tackle the complexities of managing creative projects, particularly in the realm of tabletop games, cooking, and note management. I'm excited to say that the direction for implementing this document management framework will be in the realm of the fantastical, specifically in the realm of world building and game mastery.

Today, I'm excited to dive deeper into the technical side, focusing on defining the core Document type that will serve as the backbone of this system.

Why go down this path?

As a game master and worldbuilder, I deal with an ever-growing web of characters, locations, lore, and countless other elements that make up the worlds I enjoy with my friends. However, managing all this information can be overwhelming, and details can easily get lost or be obfuscated unless you're an ace power user of your tool of choice.

In short, I wanted to create a system that not only organizes this data efficiently but also allows for scalability, flexibility as my worlds expand, and lets my players and friends have an easier time reviewing and relating to the world they're playing in.

To start, by implementing a robust Document type that extends from a generic Entity base class, I can ensure that all pieces of content in my system are treated uniformly, allowing for consistent handling of metadata, relationships, and domain events. This design choice aligns with a few of the principles from Clean Architecture and Domain-Driven Design, providing a strong foundation for future growth.

The Tree of Entities!

📚 Understanding the Core: Entity and Document Classes

At the heart of my system lies the Entity base class. This generic class provides the foundational properties and methods common to all entities in the system. By extending Entity, the Document class inherits these essential features and adds document-specific properties and behaviors.

Entity Base Class

  • Purpose: Provides unique identification, domain event handling, and common functionalities for all entities.
  • Properties:
    • _id: Unique identifier (UUID).
    • _created: Timestamp of creation.
    • _updated: Timestamp of the last update.
    • props: Properties specific to the derived class.
  • Methods:
    • Domain event handling (addDomainEvent, clearDomainEvents, etc.).
    • Validation and timestamp management.
export abstract class Entity<T> {
  private readonly _id: string;
  private readonly _created: string;
  private _updated: string;
  protected props: T;
  private _domainEvents: DomainEvent[] = [];

  constructor(props: T, id?: string, created?: string, updated?: string) {
    this._id = id ? id : uuid();
    this._created = created ? created : new Date().toISOString();
    this._updated = updated ? updated : new Date().toISOString();
    this.props = {
      ...props,
      id: this._id,
      created: this._created,
      updated: this._updated,
    };
  }

  // Domain event methods, getters, and setters...
}
Enter fullscreen mode Exit fullscreen mode

Document Class (Extends Entity)

  • Purpose: Serves as the base class for all document types in the system.
  • Additional Properties (within props):
    • title: The document's title.
    • content: Main body of the document.
    • metadata: Collection of key-value pairs for additional context.
  • Methods:
    • Document-specific behaviors and utilities.
interface DocumentProps {
  title: string;
  content: string;
  metadata: Map<string, any>;
  relationships: Relationship[];
  // Other properties as needed
}

class Document extends Entity<DocumentProps> {
  constructor(props: DocumentProps) {
    super(props);
    // Additional initialization if needed
  }

  // Document-specific methods...
}
Enter fullscreen mode Exit fullscreen mode

🔍 Defining Actors and Locations as Specialized Documents

In these early days, I need to determine the key entities required to get this project moving, knowing that there will be more to add as the project evolves. And in this context, two are relatively obvious: Actor and Location. By inheriting from Document, they gain all the base functionality and can add their own specific properties and methods, and give a good view into how the framework will need to adapt as more complex relationships eventually get made.

Actor Class (Extends Document)

  • Purpose: Represents a person or creature within the game world.
  • Additional Properties (within props):
    • name: { full name, given name, etc.}
    • ancestry: the ancestral origins of the actor, could be Human, Orc, Dragon, etc.
    • relationships: Connections to other entities.
interface ActorProps extends DocumentProps {
  name: ActorName,
  ancestry: ActorAncestry,
}

class Actor extends Document {
  constructor(props: ActorProps) {
    super(props);
    // Additional initialization if needed
  }

  // Actor-specific methods...
}
Enter fullscreen mode Exit fullscreen mode

Location Class (Extends Document)

Purpose: Represents a place within the game world.
Additional Properties (within props):
geography: Details about terrain, climate, etc.

interface LocationProps extends DocumentProps {
  geography: string;
}

class Location extends Document {
  constructor(props: LocationProps) {
    super(props);
    // Additional initialization if needed
  }

  // Location-specific methods...
}
Enter fullscreen mode Exit fullscreen mode

🔗 Class Relationships

✏️ Why This Implementation?

Choosing this structure offers several advantages:

Uniform Handling of Entities: By having all classes extend from Entity, we ensure that core functionalities like identity management and domain event handling are consistent across the system.

Extensibility: New document types can be added easily by extending Document. This makes the system flexible and adaptable to future needs.

Separation of Concerns: Entity handles generic entity behavior, Document handles document-specific behavior, and domain-specific classes like Actor and Location add their unique attributes.

Alignment with Clean Architecture: This design promotes a clear separation between domain models and other layers, enhancing maintainability and scalability.

🤔 Planning for Agents as Future Entities

Although agents are not yet part of the user-facing interface, I'm designing the system so that agents can be treated as entities in the future. By extending Entity, agents can be managed similarly to other entities, allowing for:

  • User Customization: Users could fine-tune agents to suit their needs.
  • Unified Management: Consistent handling of entities simplifies the system architecture.
  • Future-proofing: Preparing for agents as entities now avoids significant refactoring later. 🤖 Note: In our next post ("Entities Part 2, Agents"), I'll delve deeper into how agents fit into this architecture.

🔄 Delving into the Code: Understanding Entity

The Entity class is crucial to the system's architecture. Here's a closer look at its implementation:

Generics: Using generics () allows Entity to be flexible with the properties (props) it holds.

Domain Events: Entity includes methods to manage domain events, which are important for maintaining consistency and handling side effects in a decoupled manner.

Validation: The class includes a validate method to ensure that the properties meet the defined schema.

public addDomainEvent(eventDetails: ICreateDomainEvent): void {
  if (eventDetails.eventSchema) {
    validate(eventDetails.eventSchema, eventDetails.event);
  }

  const event: DomainEvent = {
    source: eventDetails.source,
    eventName: eventDetails.eventName,
    event: eventDetails.event,
    eventVersion: eventDetails.eventVersion,
    eventDateTime: this.getISOString(),
  };

  this._domainEvents.push(event);
}
Enter fullscreen mode Exit fullscreen mode

🚀 Moving Forward

With the core Document type and its relationship to Entity established, some of the steps ahead include:

Develop the Agent entity: To fulfill the goals of this project, I want semi-to-full automation in the form of Agentic Orchestration. While I could simply develop agentic interactions as a service layer, I'd like to take it a step further and consider Agents as manageable business entities.

Review our Bounded Contexts: After our two primary entity sets are established, we'll review our decisions how they fit into the architectural strategies we've settled on, what complications there may be, and how to further set the structure of the solution going forward.

Implementing Repositories: Setting up data access layers to persist and retrieve entities.

🔍 It might seem like I’m overlooking “User” as an entity, but for now, I’m designing this system for a single user to simplify development and focus on core functionalities before expanding to a multi-user environment. While references to users owning documents or entities may appear, I’ll only revisit this concept later when preparing the UX, expanding to multi-user functionality, and needing to address authorization.

Closing Thoughts

Defining Document as an extension of Entity is more than a structural decision—it’s the cornerstone of a system that can grow as needed when more content types are required. This approach balances consistency, ease of expansion, and will be used similarly to have Agents and their component entities also extend Entity, making it a solid foundation for what’s to come.

What's Next

In the upcoming "Entities Part 2, Agents," I'll explore how Agents fit into this architecture, what sub entities may be needed based on the technology we'll be leveraging, and a few exciting concepts found in AWS Bedrock and Generally that'll inform those decisions.

Join the Conversation

I’m curious to know:

  • How do you prioritize which entities are essential when designing a system?
  • What role do you envision agents playing in business operations and creative projects?
  • Imagine a tool that could enrich content dynamically with the most relevant context—how would it fit into your workflow?

"Programs must be written for people to read, and only incidentally for machines to execute." -Harold Abelson and Gerald Jay Sussman

Helpful References

Clean Architecture by Robert C. Martin - A comprehensive guide on structuring systems for maintainability and scalability.
Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans - An in-depth look at managing complex software projects through domain modeling.
Implementing Domain-Driven Design by Vaughn Vernon - Practical examples and patterns for applying DDD principles.
The Serverless Advocate aka Lee Gilmore -Insights and best practices on serverless architectures and event-driven design, whose own examples are the basis of the base entity orchestration seen here.

. .
Terabox Video Player