π³ 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.Domainproject under a folder namedEntitiesusing the shared namespacePotato.Example.Domain.
π‘ Protip: To reduce repetitive
usingdirectives, create aUsings.csfile 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
ParentIdandAncestors - 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)