Skip to content

Workspace

Overview

The Workspace system adds a fine-grained authorization layer between Tenants and Aggregates in Comby. It enables multi-project or multi-context scenarios within a single tenant, where different user groups access isolated resource sets.

Concept

Problem without Workspaces:

All identities within a tenant can access all tenant resources. There's no way to isolate resources for different projects or teams.

Solution with Workspaces:

Workspaces create isolated contexts within a tenant. Each workspace has its own members, groups with permissions, and associated aggregates (data).

Tenant (e.g., "Acme Corp")
├── Workspace "Startup 1"
│   ├── Members: [Identity A]
│   ├── Groups: ["Developers" with permissions]
│   └── Aggregates: [Customer "Apple Inc."]
└── Workspace "Startup 2"
    ├── Members: [Identity B]
    ├── Groups: ["Developers" with permissions]
    └── Aggregates: [Customer "Google LLC"]

Key Characteristics:

  • Fine-Grained Isolation: Separate resource sets within a tenant
  • Multi-Membership: Users can be members of multiple workspaces
  • Workspace Groups: Independent permission groups per workspace
  • Additive Permissions: Tenant OR workspace permissions grant access
  • Aggregate Association: All domain data belongs to workspaces
  • Transitive Membership: Workspaces can be members of other workspaces

Transitive Membership (Workspace as Member)

In addition to individual identities, entire workspaces can be added as members of other workspaces. All identities within a member workspace automatically inherit access to the host workspace with the assigned group permissions.

Workspace A (Host Workspace)
├── Identity Members: [Identity X]
├── Workspace Members: [Workspace B with "Developers" group]
│   └── Workspace B contains: [Identity Y, Identity Z]
└── Effective Access to Workspace A:
    ├── Identity X (direct member)
    ├── Identity Y (transitive via Workspace B → "Developers" permissions)
    └── Identity Z (transitive via Workspace B → "Developers" permissions)

Key Benefits:

  • Team-Based Access: Grant entire teams access without managing individual memberships
  • Permission Inheritance: Member workspace identities inherit the assigned group permissions
  • Simplified Management: Add/remove team access with a single operation
  • Multi-Level Support: Transitive membership works up to 5 levels deep (configurable)

Permission Flow:

Workspace B is member of Workspace A with group "Developers"

Identity Y is member of Workspace B with group "Engineers"

Identity Y accesses Workspace A:
    → Gets "Developers" permissions (from Workspace B's membership in A)
    → Does NOT get "Engineers" permissions (those are for Workspace B only)

Constraints:

  • Workspaces must belong to the same tenant
  • Circular references are prevented (A → B → A not allowed)
  • Self-membership is not allowed

Architecture

Hierarchy

System
└── Tenant
    ├── Tenant-Level Groups & Permissions
    ├── Identities (Users)
    └── Workspaces
        ├── Workspace Groups & Permissions
        ├── Workspace Members - Identities (Subset of Tenant Identities)
        ├── Workspace Members - Workspaces (Transitive Membership)
        └── Aggregates (Data isolated per Workspace)

Workspace is connected to:

  • Tenant: Parent entity, each workspace belongs to exactly one tenant
  • Identity: Users can be members of multiple workspaces
  • Groups: Workspace-specific groups with permissions
  • Aggregates: Domain entities (Customer, Invoice, etc.) belong to workspaces

Domain Model

Permission Model

Additive Permissions

The system uses an ADDITIVE permission model:

Access Granted = (Tenant Permission) OR (Workspace Permission)

Key Rules:

  • Identity needs EITHER tenant-level OR workspace-level permission
  • Identity must be workspace member to access workspace resources
  • System admins bypass all checks

Examples:

ScenarioTenant PermissionWorkspace MemberWorkspace PermissionResult
Tenant AdminCustomer.CreateNo-Access granted
Workspace-Only UserNoYesCustomer.CreateAccess granted
Non-MemberCustomer.CreateNo-Access denied
Member Without PermissionNoYesNoAccess denied

Authorization Flow

  1. System Admin Check → If yes: Allowed
  2. Cross-Tenant Check → Sender and target must have same tenant
  3. Workspace Membership Check → Must be member if TargetWorkspaceUuid is set
  4. Aggregate-Workspace Validation → Aggregate must belong to workspace
  5. Additive Permission Check → Tenant permission OR workspace permission

Use Cases

1. Agency Managing Multiple Client Projects

Tenant: "Acme Agency"
├── Workspace: "Client A - Nike Campaign"
│   ├── Members: [Designer1, Copywriter1, Manager1]
│   ├── Groups: ["Project Admins", "Designers", "Copywriters"]
│   └── Aggregates: [Customer "Nike", Invoice #1001]
└── Workspace: "Client B - Adidas Campaign"
    ├── Members: [Designer2, Copywriter2, Manager1]
    ├── Groups: ["Project Admins", "Designers", "Copywriters"]
    └── Aggregates: [Customer "Adidas", Invoice #2001]

Result: Designer1 can only see Nike data, Designer2 can only see Adidas data

2. SaaS Platform with Sub-Organizations

Tenant: "ACME Corp"
├── Workspace: "Engineering Team"
│   ├── Members: [Engineer1, Engineer2, Tech Lead]
│   ├── Groups: ["Leads", "Engineers"]
│   └── Aggregates: [Projects, Issues, PRs]
├── Workspace: "Marketing Team"
│   ├── Members: [Marketer1, Marketer2, Marketing Manager]
│   ├── Groups: ["Managers", "Marketers"]
│   └── Aggregates: [Campaigns, Content]
└── Workspace: "Sales Team"
    ├── Members: [Sales Rep1, Sales Rep2, Sales Manager]
    ├── Groups: ["Managers", "Sales Reps"]
    └── Aggregates: [Leads, Opportunities]

Result: Complete data isolation between departments

3. Incubator/Accelerator Managing Startups

Tenant: "Y Combinator"
├── Workspace: "Startup 1 - FinTech"
│   ├── Members: [Founder1, Founder2, Mentor1]
│   ├── Groups: ["Founders", "Mentors", "Advisors"]
│   └── Aggregates: [Business Plan, Metrics, Documents]
├── Workspace: "Startup 2 - HealthTech"
│   ├── Members: [Founder3, Mentor1, Advisor1]
│   ├── Groups: ["Founders", "Mentors", "Advisors"]
│   └── Aggregates: [Business Plan, Metrics, Documents]
└── Workspace: "Startup 3 - EdTech"
    ├── Members: [Founder4, Founder5, Mentor2]
    ├── Groups: ["Founders", "Mentors", "Advisors"]
    └── Aggregates: [Business Plan, Metrics, Documents]

Result: Each startup's data is isolated; Mentor1 can access Startup 1 and 2

4. Consulting Firm with Multiple Engagements

Tenant: "McKinsey"
├── Workspace: "Client Project A"
│   ├── Members: [Consultant1, Consultant2, Partner1]
│   └── Aggregates: [Reports, Deliverables, Invoices]
├── Workspace: "Client Project B"
│   ├── Members: [Consultant3, Consultant4, Partner1]
│   └── Aggregates: [Reports, Deliverables, Invoices]
└── Workspace: "Internal Operations"
    ├── Members: [All Staff]
    └── Aggregates: [HR Documents, Policies]

Result: Client data isolation; Partner1 oversees multiple projects

5. Holding Company with Transitive Workspace Access

Tenant: "TechHolding Inc"
├── Workspace: "Shared Services" (Host)
│   ├── Identity Members: [IT Admin, Finance Lead]
│   ├── Workspace Members:
│   │   ├── "Startup Alpha" → Group: "Basic Access"
│   │   └── "Startup Beta" → Group: "Basic Access"
│   └── Aggregates: [Shared Tools, Templates, Guidelines]
├── Workspace: "Startup Alpha"
│   ├── Members: [Founder1, Dev1, Dev2]
│   └── Aggregates: [Alpha's Products, Alpha's Customers]
└── Workspace: "Startup Beta"
    ├── Members: [Founder2, Designer1]
    └── Aggregates: [Beta's Products, Beta's Customers]

Result:
- Founder1, Dev1, Dev2 can access "Shared Services" with "Basic Access" permissions
- Founder2, Designer1 can access "Shared Services" with "Basic Access" permissions
- Startup Alpha members cannot see Startup Beta data (and vice versa)
- IT Admin can manage all shared resources

6. Multi-Team Project with Cross-Access

Tenant: "Enterprise Corp"
├── Workspace: "Platform Team"
│   ├── Members: [PlatformDev1, PlatformDev2]
│   └── Aggregates: [APIs, Infrastructure]
├── Workspace: "Product A Team"
│   ├── Members: [ProductDev1, ProductDev2]
│   ├── Workspace Members: ["Platform Team" → Group: "API Consumers"]
│   └── Aggregates: [Product A Features]
└── Workspace: "Product B Team"
    ├── Members: [ProductDev3, ProductDev4]
    ├── Workspace Members: ["Platform Team" → Group: "API Consumers"]
    └── Aggregates: [Product B Features]

Result:
- Platform Team members (PlatformDev1, PlatformDev2) automatically have
  "API Consumers" access to both Product A and Product B workspaces
- Product teams remain isolated from each other
- Adding a new platform developer grants them access to all product workspaces

Features

  • Multi-workspace membership per identity
  • Workspace-level groups and permissions
  • Additive permission model (tenant OR workspace)
  • Backwards compatible (works with existing tenant-level auth)
  • Aggregate isolation per workspace
  • System admin override
  • Owner designation per workspace
  • Custom attributes support
  • Workspace invitations
  • Group-based access control
  • Transitive membership (workspace as member of another workspace)
  • Permission inheritance through workspace membership

Best Practices

Naming

  • Use descriptive names: "Project Alpha", "Client XYZ"
  • Include project identifiers: "Nike Campaign 2024"
  • Avoid generic names: "WS1", "Workspace 123", "Test"

Group Structure

Recommended pattern:

Workspace
├── Group: "Owners"   → All permissions (*.*)
├── Group: "Admins"   → Most permissions (domain.*)
├── Group: "Members"  → Standard CRUD operations
└── Group: "Viewers"  → Read-only access (*.list, *.get)

Example permissions:

go
Owners:  []string{...all...}
Admins:  []string{"Customer.Create", "Customer.Update", "Invoice.Create", "Invoice.List"}
Members: []string{"Customer.Create", "Invoice.List"}
Viewers: []string{"Invoice.List"}

Permission Granularity

  • Workspace-level: Resource-specific operations (Customer.Create, Invoice.Update)
  • Tenant-level: Administrative operations (Tenant.Update, Identity.Create)

Good separation:

go
Tenant Groups:
- "System Administrators" → Tenant.*, Identity.*, Workspace.*
- "Tenant Managers" → Identity.Create, Workspace.Create

Workspace Groups:
- "Project Admins" → Customer.*, Invoice.*, Asset.*
- "Contributors" → Customer.Create, Customer.Update

Adding Members

go
// - Good: Add members with specific groups
cmd := &command.WorkspaceCommandAddMember{
    WorkspaceUuid: "workspace-uuid",
    IdentityUuid:  "identity-uuid",
    GroupUuids:    []string{"members-group-uuid"},
}

Workspace Lifecycle

  1. Creation: Set owner and initial groups
  2. Initial Setup: Add default groups (owners, members, viewers)
  3. Member Onboarding: Add members with appropriate groups
  4. Permission Management: Adjust group permissions as needed
  5. Archival: Delete workspace when project completes

Resource Organization

  • All aggregates should have workspace context
  • Use workspace-level webhooks for workspace events
  • Use workspace-level assets for project files
  • Keep workspace count manageable (< 100 per tenant)

Authorization Examples

Example 1: Workspace Member with Workspace Permission

go
// Setup
Identity: alice
Tenant Groups: []
Workspace Member: Yes (Workspace A)
Workspace Groups: ["Developers" with "Customer.Create"]

// Request
TargetWorkspaceUuid: "workspace-a-uuid"
Permission: "Customer.Create"

// Result: Access Granted
// Reason: Workspace permission granted

Example 2: Tenant Admin Accessing Workspace

go
// Setup
Identity: bob
Tenant Groups: ["Admins" with "Customer.Create"]
Workspace Member: No

// Request
TargetWorkspaceUuid: "workspace-a-uuid"
Permission: "Customer.Create"

// Result: Access Denied
// Reason: Not a workspace member

Example 3: Workspace Member without Permission

go
// Setup
Identity: charlie
Tenant Groups: []
Workspace Member: Yes (Workspace A)
Workspace Groups: ["Viewers" with "customer.list"]

// Request
TargetWorkspaceUuid: "workspace-a-uuid"
Permission: "Customer.Create"

// Result: Access Denied
// Reason: No "Customer.Create" permission (tenant OR workspace)

Example 4: System Admin

go
// Setup
Identity: dave
System Admin: Yes (SYSTEM_TENANT_GROUP_ADMIN)

// Request
TargetWorkspaceUuid: "workspace-a-uuid"
Permission: "Customer.Create"

// Result: Access Granted
// Reason: System admin bypasses all checks

Example 5: Transitive Membership Access

go
// Setup
Identity: eve
Tenant Groups: []
Workspace Member of "Workspace B": Yes (with group "Engineers")
Workspace B is Member of "Workspace A": Yes (with group "Developers")

// "Developers" group in Workspace A has permission "Customer.Create"
// "Engineers" group in Workspace B has permission "Code.Deploy"

// Request to Workspace A
TargetWorkspaceUuid: "workspace-a-uuid"
Permission: "Customer.Create"

// Result: Access Granted
// Reason: Eve is transitive member of Workspace A via Workspace B,
//         and inherits "Developers" group permissions

Example 6: Transitive Membership - Permission Not Inherited

go
// Setup (same as Example 5)
Identity: eve
Workspace B is Member of "Workspace A": Yes (with group "Developers")
Eve is member of Workspace B with group "Engineers"

// Request to Workspace A
TargetWorkspaceUuid: "workspace-a-uuid"
Permission: "Admin.Delete"  // Not in "Developers" group

// Result: Access Denied
// Reason: Eve only inherits "Developers" permissions in Workspace A,
//         not her "Engineers" permissions from Workspace B

Troubleshooting

"Permission Denied" when identity should have access

Check:

  1. Is the identity a workspace member?

    go
    GetAuthReadmodel().IsWorkspaceMember(workspaceUuid, identityUuid)
  2. Does the identity have the permission?

    go
    permissions := GetAuthReadmodel().GetWorkspacePermissions(workspaceUuid, identityUuid)
    fmt.Printf("Permissions: %v\n", permissions)
  3. Is TargetWorkspaceUuid set in RequestContext?

    go
    reqCtx.TargetWorkspaceUuid // Must be set!
  4. Does the aggregate belong to the workspace?

    go
    GetAuthReadmodel().IsValidWorkspaceAggregate(workspaceUuid, aggregateUuid)

Debug:

go
// Check workspace membership
member := GetAuthReadmodel().IsWorkspaceMember("workspace-uuid", "identity-uuid")
fmt.Printf("Is member: %v\n", member)

// Check workspace permissions
perms := GetAuthReadmodel().GetWorkspacePermissions("workspace-uuid", "identity-uuid")
fmt.Printf("Workspace permissions: %v\n", perms)

// Check tenant permissions
tenantPerms := GetAuthReadmodel().GetTenantPermissions("tenant-uuid", "identity-uuid")
fmt.Printf("Tenant permissions: %v\n", tenantPerms)

Aggregate not appearing in workspace

Cause: The aggregate was created without workspace context.

Solution: Events must have WorkspaceUuid in request context:

go
reqCtx := comby.NewRequestContext()
reqCtx.TargetWorkspaceUuid = "workspace-uuid"
cmd.SetReqCtx(reqCtx)

User can't see workspace in list

Cause: User is not a member of the workspace.

Solution:

  1. Verify membership:
    go
    qry := &query.WorkspaceQueryListByIdentity{
        TenantUuid:   "tenant-uuid",
        IdentityUuid: "identity-uuid",
    }
  2. Add user to workspace if needed

Group permissions not working

Cause: Identity not assigned to the group.

Solution:

go
// Check identity's groups in workspace
member := GetWorkspaceMember("workspace-uuid", "identity-uuid")
fmt.Printf("GroupUuids: %v\n", member.GroupUuids)

// Add identity to group if missing
cmd := &command.WorkspaceCommandUpdateMember{
    WorkspaceUuid: "workspace-uuid",
    IdentityUuid:  "identity-uuid",
    GroupUuids:    []string{"group-uuid"},
}

Cross-workspace data access

Cause: Attempting to access data from wrong workspace.

Solution:

  • Each aggregate belongs to exactly one workspace
  • Use correct TargetWorkspaceUuid in request context
  • Query workspace-specific data:
    go
    reqCtx.TargetWorkspaceUuid = "correct-workspace-uuid"

Transitive membership not working

Cause: Identity should have access via workspace membership but gets denied.

Check:

  1. Is the member workspace actually added as a workspace member?

    go
    memberWorkspaces := GetAuthReadmodel().GetMemberWorkspaces(hostWorkspaceUuid)
    fmt.Printf("Member workspaces: %v\n", memberWorkspaces)
  2. Is the identity a member of the member workspace?

    go
    isMember := GetAuthReadmodel().IsWorkspaceMember(memberWorkspaceUuid, identityUuid)
    fmt.Printf("Is member of source workspace: %v\n", isMember)
  3. Does the workspace membership have the correct groups assigned?

    go
    // Check which groups the member workspace has in the host workspace
    for _, mw := range memberWorkspaces {
        if mw.WorkspaceUuid == memberWorkspaceUuid {
            fmt.Printf("Assigned groups: %v\n", mw.GroupUuids)
        }
    }
  4. Check transitive permissions:

    go
    permissions := GetAuthReadmodel().GetWorkspacePermissionsTransitive(
        hostWorkspaceUuid, identityUuid, 5)
    fmt.Printf("Transitive permissions: %v\n", permissions)

Common Issues:

  • Workspaces not in the same tenant (required)
  • Circular reference detected and blocked
  • Max transitive depth exceeded (default: 5 levels)
  • Identity not a member of the source workspace

Integration with Other Domains

Identity Domain

Identities can be members of multiple workspaces:

go
// Identity can join multiple workspaces
AddMember("workspace-a-uuid", "identity-uuid", groups)
AddMember("workspace-b-uuid", "identity-uuid", groups)

Invitation Domain

Workspace invitations:

go
// Invite user to workspace
cmd := &invitation.InvitationCommandCreate{
    WorkspaceUuid: "workspace-uuid",
    Email:         "user@example.com",
    GroupUuids:    []string{"members-group-uuid"},
}

Asset Domain

Workspace-scoped assets:

go
// Upload asset to workspace
cmd := &asset.AssetCommandAdd{
    WorkspaceUuid: "workspace-uuid",
    // ...
}

Webhook Domain

Workspace-scoped webhooks:

go
// Create workspace webhook
cmd := &webhook.WebhookCommandAdd{
    WorkspaceUuid:       "workspace-uuid",
    DomainEvtIdentifier: "Customer.CustomerCreatedEvent",
    WebhookUrl:          "https://example.com/webhook",
}

All Custom Domains

Any custom aggregate can be workspace-scoped:

go
// Set workspace context for custom domain
reqCtx.TargetWorkspaceUuid = "workspace-uuid"
cmd.SetReqCtx(reqCtx)

Advanced Topics

Multi-Workspace Queries

Query data across workspaces (if user is member):

go
// Get all workspaces user is member of
workspaces := GetWorkspacesByIdentity(identityUuid)

// Query each workspace
for _, workspace := range workspaces {
    reqCtx.TargetWorkspaceUuid = workspace.WorkspaceUuid
    results := QueryCustomers(reqCtx)
    // Aggregate results
}

Workspace Templates

Use predefined group templates:

go
var WorkspaceTemplates = map[string][]WorkspaceGroup{
    "agency-project": {
        {Name: "Project Managers", Permissions: []string{"*.*"}},
        {Name: "Designers", Permissions: []string{"asset.*", "customer.list"}},
        {Name: "Copywriters", Permissions: []string{"content.*", "customer.list"}},
    },
    "development-team": {
        {Name: "Tech Leads", Permissions: []string{"*.*"}},
        {Name: "Developers", Permissions: []string{"code.*", "issue.*"}},
        {Name: "QA Engineers", Permissions: []string{"test.*", "issue.create"}},
    },
}

Commands

WorkspaceCommandAddGroup

Domain Command Struct:

go
type WorkspaceCommandAddGroup struct {
	WorkspaceUuid string   `json:"workspaceUuid"`
	GroupUuid     string   `json:"groupUuid"`
	Name          string   `json:"name"`
	Permissions   []string `json:"permissions,omitempty"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandAddGroup(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandAddGroup) ([]comby.Event, error)

WorkspaceCommandAddMember

Domain Command Struct:

go
type WorkspaceCommandAddMember struct {
	WorkspaceUuid string   `json:"workspaceUuid"`
	IdentityUuid  string   `json:"identityUuid"`
	GroupUuids    []string `json:"groupUuids,omitempty"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandAddMember(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandAddMember) ([]comby.Event, error)

WorkspaceCommandAddWorkspaceMember

WorkspaceCommandAddWorkspaceMember adds another workspace as a member of this workspace. All identities in the member workspace will inherit access to this workspace with the assigned groups.

Domain Command Struct:

go
type WorkspaceCommandAddWorkspaceMember struct {
	WorkspaceUuid       string   `json:"workspaceUuid"`        // The workspace to add the member to
	MemberWorkspaceUuid string   `json:"memberWorkspaceUuid"`  // The workspace to add as member
	GroupUuids          []string `json:"groupUuids,omitempty"` // Groups to assign to the workspace member
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandAddWorkspaceMember(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandAddWorkspaceMember) ([]comby.Event, error)

WorkspaceCommandCreate

Domain Command Struct:

go
type WorkspaceCommandCreate struct {
	TenantUuid        string `json:"tenantUuid"`
	WorkspaceUuid     string `json:"workspaceUuid"`
	Name              string `json:"name"`
	Description       string `json:"description,omitempty"`
	OwnerIdentityUuid string `json:"ownerIdentityUuid"`
	Attributes        string `json:"attributes,omitempty"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandCreate(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandCreate) ([]comby.Event, error)

WorkspaceCommandRemove

Domain Command Struct:

go
type WorkspaceCommandRemove struct {
	WorkspaceUuid string `json:"workspaceUuid"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandRemove(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemove) ([]comby.Event, error)

WorkspaceCommandRemoveGroup

Domain Command Struct:

go
type WorkspaceCommandRemoveGroup struct {
	WorkspaceUuid string `json:"workspaceUuid"`
	GroupUuid     string `json:"groupUuid"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandRemoveGroup(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemoveGroup) ([]comby.Event, error)

WorkspaceCommandRemoveMember

Domain Command Struct:

go
type WorkspaceCommandRemoveMember struct {
	WorkspaceUuid string `json:"workspaceUuid"`
	IdentityUuid  string `json:"identityUuid"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandRemoveMember(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemoveMember) ([]comby.Event, error)

WorkspaceCommandRemoveWorkspaceMember

WorkspaceCommandRemoveWorkspaceMember removes a workspace from the members of this workspace.

Domain Command Struct:

go
type WorkspaceCommandRemoveWorkspaceMember struct {
	WorkspaceUuid       string `json:"workspaceUuid"`       // The workspace to remove the member from
	MemberWorkspaceUuid string `json:"memberWorkspaceUuid"` // The workspace to remove as member
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandRemoveWorkspaceMember(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemoveWorkspaceMember) ([]comby.Event, error)

WorkspaceCommandUpdate

Domain Command Struct:

go
type WorkspaceCommandUpdate struct {
	WorkspaceUuid string   `json:"workspaceUuid"`
	Name          string   `json:"name,omitempty"`
	Description   string   `json:"description,omitempty"`
	Attributes    string   `json:"attributes,omitempty"`
	PatchedFields []string `json:"patchedFields" doc:"list of fields that should be patched - comma separated" example:"field1,field2"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandUpdate(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandUpdate) ([]comby.Event, error)

WorkspaceCommandUpdateGroup

Domain Command Struct:

go
type WorkspaceCommandUpdateGroup struct {
	WorkspaceUuid string   `json:"workspaceUuid"`
	GroupUuid     string   `json:"groupUuid"`
	Permissions   []string `json:"permissions"`
	PatchedFields []string `json:"patchedFields"`
}

Domain Command Handling Method:

go
func (cs *commandHandler) WorkspaceCommandUpdateGroup(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandUpdateGroup) ([]comby.Event, error)

Queries

Domain Query Structs:

Domain Query Responses:

WorkspaceQueryGet

Domain Query Struct:

go
type WorkspaceQueryGet struct {
	WorkspaceUuid  string `json:"workspaceUuid"`
	IncludeHistory bool   `json:"includeHistory,omitempty"`
}

Domain Query Handling Method:

go
func (qs *queryHandler) WorkspaceQueryGet(ctx context.Context, qry comby.Query, domainQry *WorkspaceQueryGet) (*WorkspaceQueryItemResponse, error)

WorkspaceQueryList

Domain Query Struct:

go
type WorkspaceQueryList struct {
	TenantUuid     string `json:"tenantUuid"`
	Page           int64  `json:"page,omitempty"`
	PageSize       int64  `json:"pageSize,omitempty"`
	OrderBy        string `json:"orderBy,omitempty"`
	Attributes     string `json:"attributes,omitempty"`
	IncludeHistory bool   `json:"includeHistory,omitempty"`
}

Domain Query Handling Method:

go
func (qs *queryHandler) WorkspaceQueryList(ctx context.Context, qry comby.Query, domainQry *WorkspaceQueryList) (*WorkspaceQueryListResponse, error)

WorkspaceQueryListByIdentity

Domain Query Struct:

go
type WorkspaceQueryListByIdentity struct {
	TenantUuid   string `json:"tenantUuid"`
	IdentityUuid string `json:"identityUuid"`
	Page         int64  `json:"page,omitempty"`
	PageSize     int64  `json:"pageSize,omitempty"`
	OrderBy      string `json:"orderBy,omitempty"`
	Attributes   string `json:"attributes,omitempty"`
}

Domain Query Handling Method:

go
func (qs *queryHandler) WorkspaceQueryListByIdentity(ctx context.Context, qry comby.Query, domainQry *WorkspaceQueryListByIdentity) (*WorkspaceQueryListResponse, error)

WorkspaceQueryItemResponse

go
type WorkspaceQueryItemResponse struct {
	Item *readmodel.WorkspaceModel `json:"item,omitempty"`
}

WorkspaceQueryListResponse

go
type WorkspaceQueryListResponse struct {
	Items    []*readmodel.WorkspaceModel `json:"items,omitempty"`
	Total    int64                       `json:"total,omitempty"`
	Page     int64                       `json:"page,omitempty"`
	PageSize int64                       `json:"pageSize,omitempty"`
}

Events

WorkspaceGroupAddedEvent

Domain Event Struct:

go
type WorkspaceGroupAddedEvent struct {
	GroupUuid       string   `json:"groupUuid"`
	Name            string   `json:"name"`
	PermissionNames []string `json:"permissionNames"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceGroupAddedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceGroupAddedEvent) (error)

WorkspaceMemberAddedEvent

Domain Event Struct:

go
type WorkspaceMemberAddedEvent struct {
	IdentityUuid string   `json:"identityUuid"`
	GroupUuids   []string `json:"groupUuids"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceMemberAddedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceMemberAddedEvent) (error)

WorkspaceWorkspaceMemberAddedEvent

WorkspaceWorkspaceMemberAddedEvent is emitted when a workspace is added as a member to another workspace. This enables transitive membership: all identities in the member workspace gain access to this workspace.

Domain Event Struct:

go
type WorkspaceWorkspaceMemberAddedEvent struct {
	MemberWorkspaceUuid string   `json:"memberWorkspaceUuid"` // The workspace that becomes a member
	GroupUuids          []string `json:"groupUuids"`          // Groups assigned to this workspace member
}

Domain Event Handling Method: WorkspaceWorkspaceMemberAddedEvent handler applies the event to the aggregate state.

go
func (agg *Workspace) WorkspaceWorkspaceMemberAddedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceWorkspaceMemberAddedEvent) (error)

WorkspaceCreatedEvent

Domain Event Struct:

go
type WorkspaceCreatedEvent struct {
	TenantUuid        string `json:"tenantUuid"`
	Name              string `json:"name"`
	Description       string `json:"description,omitempty"`
	OwnerIdentityUuid string `json:"ownerIdentityUuid"`
	Attributes        string `json:"attributes,omitempty"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceCreatedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceCreatedEvent) (error)

WorkspaceRemovedEvent

Domain Event Struct:

go
type WorkspaceRemovedEvent struct {
	Reason string `json:"reason,omitempty"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceRemovedEvent) (error)

WorkspaceGroupRemovedEvent

Domain Event Struct:

go
type WorkspaceGroupRemovedEvent struct {
	GroupUuid string `json:"groupUuid"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceGroupRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceGroupRemovedEvent) (error)

WorkspaceMemberRemovedEvent

Domain Event Struct:

go
type WorkspaceMemberRemovedEvent struct {
	IdentityUuid string `json:"identityUuid"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceMemberRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceMemberRemovedEvent) (error)

WorkspaceWorkspaceMemberRemovedEvent

WorkspaceWorkspaceMemberRemovedEvent is emitted when a workspace is removed as a member from another workspace.

Domain Event Struct:

go
type WorkspaceWorkspaceMemberRemovedEvent struct {
	MemberWorkspaceUuid string `json:"memberWorkspaceUuid"` // The workspace that is removed as member
}

Domain Event Handling Method: WorkspaceWorkspaceMemberRemovedEvent handler applies the event to the aggregate state.

go
func (agg *Workspace) WorkspaceWorkspaceMemberRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceWorkspaceMemberRemovedEvent) (error)

WorkspaceUpdatedEvent

Domain Event Struct:

go
type WorkspaceUpdatedEvent struct {
	Name          string   `json:"name,omitempty"`
	Description   string   `json:"description,omitempty"`
	Attributes    string   `json:"attributes,omitempty"`
	PatchedFields []string `json:"patchedFields,omitempty"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceUpdatedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceUpdatedEvent) (error)

WorkspaceGroupUpdatedEvent

Domain Event Struct:

go
type WorkspaceGroupUpdatedEvent struct {
	GroupUuid       string   `json:"groupUuid"`
	PermissionNames []string `json:"permissionNames"`
}

Domain Event Handling Method:

go
func (agg *Workspace) WorkspaceGroupUpdatedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceGroupUpdatedEvent) (error)

Aggregate

Aggregate Struct:

go
type Workspace struct {
	*comby.BaseAggregate
	// References
	TenantUuid string
	// Entities
	Groups           []*WorkspaceGroup           // Workspace groups with permissions
	Members          []*WorkspaceMember          // Identity members
	MemberWorkspaces []*WorkspaceMemberWorkspace // Workspace members (transitive membership)
	// Value Objects
	Name              string
	Description       string
	OwnerIdentityUuid string
}

Methods

AddGroup

go
func (agg *Workspace) AddGroup(opts ) (error)

AddMember

go
func (agg *Workspace) AddMember(opts ) (error)

AddWorkspaceMember

AddWorkspaceMember adds another workspace as a member of this workspace. All identities in the member workspace will inherit access to this workspace with the assigned groups.

go
func (agg *Workspace) AddWorkspaceMember(opts ) (error)

Create

go
func (agg *Workspace) Create(opts ) (error)

Remove

go
func (agg *Workspace) Remove(opts ) (error)

RemoveGroup

go
func (agg *Workspace) RemoveGroup(opts ) (error)

RemoveMember

go
func (agg *Workspace) RemoveMember(opts ) (error)

RemoveWorkspaceMember

RemoveWorkspaceMember removes a workspace from the members of this workspace.

go
func (agg *Workspace) RemoveWorkspaceMember(opts ) (error)

Update

go
func (agg *Workspace) Update(opts ) (error)

UpdateGroup

go
func (agg *Workspace) UpdateGroup(opts ) (error)

Event Handlers

WorkspaceReadmodel

Domain EventMethod
aggregate.WorkspaceCreatedEventWorkspaceCreatedEvent
aggregate.WorkspaceUpdatedEventWorkspaceUpdatedEvent
aggregate.WorkspaceRemovedEventWorkspaceRemovedEvent
aggregate.WorkspaceMemberAddedEventWorkspaceMemberAddedEvent
aggregate.WorkspaceMemberRemovedEventWorkspaceMemberRemovedEvent
aggregate.WorkspaceGroupAddedEventWorkspaceGroupAddedEvent
aggregate.WorkspaceGroupUpdatedEventWorkspaceGroupUpdatedEvent
aggregate.WorkspaceGroupRemovedEventWorkspaceGroupRemovedEvent
aggregate.WorkspaceWorkspaceMemberAddedEventWorkspaceWorkspaceMemberAddedEvent
aggregate.WorkspaceWorkspaceMemberRemovedEventWorkspaceWorkspaceMemberRemovedEvent

Custom Permissions

NameTypeComment
WorkspaceQueryListByIdentityQueryList workspaces by identityUuid