Table of Contents

🌳 Modeling Your Domain with Nodes

Potato uses a tree-based domain model to represent entities. Every object β€” whether a user, group, or permission β€” is a Node within this tree. This structure enables clear hierarchical relationships, fine-grained permission scoping, and flexible filtering for both data traversal and access control.

This guide walks you through defining your domain nodes using Potato’s tree abstractions, starting from a shared base type and extending it to real-world domain objects.

πŸ“ Place all types in the Potato.Example.Domain project under a folder named Entities using the shared namespace Potato.Example.Domain.

πŸ’‘ Protip: To reduce repetitive using directives, create a Usings.cs file in your project root:

global using static LanguageExt.Prelude;
global using static Potato.PotatoModule;
global using LanguageExt;
global using Potato;

πŸ“„ Entities/Node.cs

The Node type is the foundation for all entities in your domain model. It encapsulates the core concepts needed for hierarchical relationships, including:

  • Globally unique identity
  • Tree ancestry via ParentId and Ancestors
  • Logical paths (optional)
  • Automatic version generation for optimistic concurrency
namespace Potato.Example.Domain;

/// <summary>
/// Represents a basic node in a hierarchical tree structure.
/// Supports ancestry tracking and concurrency control.
/// </summary>
public record Node(
    Guid? Id,
    Guid? ParentId,
    string? DisplayName = null
) : PotatoTreeNode(Id, ParentId, Ancestors: Empty, Path: Empty, Version: GenerateVersion()), PotatoAggregateRoot;

πŸ“„ Entities/UserNode.cs

The UserNode represents an individual user in your system. It inherits from the base Node and adds a typed Username field, which also acts as its display name.

namespace Potato.Example.Domain;

/// <summary>
/// Represents a user node within the domain tree.
/// Inherits base metadata and uses the username as the display label.
/// </summary>
public record UserNode(
    Guid? Id,
    Guid? ParentId,
    string Username
) : Node(Id, ParentId, DisplayName: Username);

πŸ“„ Entities/UserGroupNode.cs

User groups are used to organize users into hierarchical collections. These nodes behave like folders or organizational units and can contain both users and subgroups.

namespace Potato.Example.Domain;

/// <summary>
/// Represents a group node used to organize users or subgroups.
/// Stores the group name as its display name.
/// </summary>
public record UserGroupNode(
    Guid? Id,
    Guid? ParentId,
    string GroupName
) : Node(Id, ParentId, DisplayName: GroupName);

πŸ“„ Entities/RootNode.cs

The RootNode serves as the entry point of your domain tree. It anchors all other nodes and is typically inserted once during system initialization.

namespace Potato.Example.Domain;

/// <summary>
/// Represents the root node of the domain tree.
/// Serves as the top-level container for all other nodes.
/// </summary>
public record RootNode(
    Guid? Id
) : Node(Id, ParentId: null, DisplayName: "Root")
{
    public static readonly RootNode Instance = new(
        Id: Guid.Parse("b1136b63-018a-42c7-9a6d-d5a32603d077")
    );
}

---

## βœ… Summary

You now have:

- βœ… A **base `Node`** type for identity, ancestry, and concurrency
- βœ… A **`UserNode`** for representing individual users
- βœ… A **`UserGroupNode`** for nesting users and groups
- βœ… A **`RootNode`** singleton to anchor the tree

➑️ **Next:** [Setup MongoDB and Initialize the Tree](setup-mongodb-and-initialize-the-tree.md)