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)Related Entities
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:
| Scenario | Tenant Permission | Workspace Member | Workspace Permission | Result |
|---|---|---|---|---|
| Tenant Admin | Customer.Create | No | - | Access granted |
| Workspace-Only User | No | Yes | Customer.Create | Access granted |
| Non-Member | Customer.Create | No | - | Access denied |
| Member Without Permission | No | Yes | No | Access denied |
Authorization Flow
- System Admin Check → If yes: Allowed
- Cross-Tenant Check → Sender and target must have same tenant
- Workspace Membership Check → Must be member if
TargetWorkspaceUuidis set - Aggregate-Workspace Validation → Aggregate must belong to workspace
- 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 data2. 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 departments3. 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 24. 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 projects5. 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 resources6. 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 workspacesFeatures
- 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:
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:
Tenant Groups:
- "System Administrators" → Tenant.*, Identity.*, Workspace.*
- "Tenant Managers" → Identity.Create, Workspace.Create
Workspace Groups:
- "Project Admins" → Customer.*, Invoice.*, Asset.*
- "Contributors" → Customer.Create, Customer.UpdateAdding Members
// - Good: Add members with specific groups
cmd := &command.WorkspaceCommandAddMember{
WorkspaceUuid: "workspace-uuid",
IdentityUuid: "identity-uuid",
GroupUuids: []string{"members-group-uuid"},
}Workspace Lifecycle
- Creation: Set owner and initial groups
- Initial Setup: Add default groups (owners, members, viewers)
- Member Onboarding: Add members with appropriate groups
- Permission Management: Adjust group permissions as needed
- 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
// 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 grantedExample 2: Tenant Admin Accessing Workspace
// 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 memberExample 3: Workspace Member without Permission
// 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
// 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 checksExample 5: Transitive Membership Access
// 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 permissionsExample 6: Transitive Membership - Permission Not Inherited
// 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 BTroubleshooting
"Permission Denied" when identity should have access
Check:
Is the identity a workspace member?
goGetAuthReadmodel().IsWorkspaceMember(workspaceUuid, identityUuid)Does the identity have the permission?
gopermissions := GetAuthReadmodel().GetWorkspacePermissions(workspaceUuid, identityUuid) fmt.Printf("Permissions: %v\n", permissions)Is
TargetWorkspaceUuidset in RequestContext?goreqCtx.TargetWorkspaceUuid // Must be set!Does the aggregate belong to the workspace?
goGetAuthReadmodel().IsValidWorkspaceAggregate(workspaceUuid, aggregateUuid)
Debug:
// 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:
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:
- Verify membership:go
qry := &query.WorkspaceQueryListByIdentity{ TenantUuid: "tenant-uuid", IdentityUuid: "identity-uuid", } - Add user to workspace if needed
Group permissions not working
Cause: Identity not assigned to the group.
Solution:
// 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
TargetWorkspaceUuidin 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:
Is the member workspace actually added as a workspace member?
gomemberWorkspaces := GetAuthReadmodel().GetMemberWorkspaces(hostWorkspaceUuid) fmt.Printf("Member workspaces: %v\n", memberWorkspaces)Is the identity a member of the member workspace?
goisMember := GetAuthReadmodel().IsWorkspaceMember(memberWorkspaceUuid, identityUuid) fmt.Printf("Is member of source workspace: %v\n", isMember)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) } }Check transitive permissions:
gopermissions := 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:
// Identity can join multiple workspaces
AddMember("workspace-a-uuid", "identity-uuid", groups)
AddMember("workspace-b-uuid", "identity-uuid", groups)Invitation Domain
Workspace invitations:
// Invite user to workspace
cmd := &invitation.InvitationCommandCreate{
WorkspaceUuid: "workspace-uuid",
Email: "user@example.com",
GroupUuids: []string{"members-group-uuid"},
}Asset Domain
Workspace-scoped assets:
// Upload asset to workspace
cmd := &asset.AssetCommandAdd{
WorkspaceUuid: "workspace-uuid",
// ...
}Webhook Domain
Workspace-scoped webhooks:
// 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:
// 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):
// 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:
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
- WorkspaceCommandAddMember
- WorkspaceCommandAddWorkspaceMember
- WorkspaceCommandCreate
- WorkspaceCommandRemove
- WorkspaceCommandRemoveGroup
- WorkspaceCommandRemoveMember
- WorkspaceCommandRemoveWorkspaceMember
- WorkspaceCommandUpdate
- WorkspaceCommandUpdateGroup
WorkspaceCommandAddGroup
Domain Command Struct:
type WorkspaceCommandAddGroup struct {
WorkspaceUuid string `json:"workspaceUuid"`
GroupUuid string `json:"groupUuid"`
Name string `json:"name"`
Permissions []string `json:"permissions,omitempty"`
}Domain Command Handling Method:
func (cs *commandHandler) WorkspaceCommandAddGroup(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandAddGroup) ([]comby.Event, error)WorkspaceCommandAddMember
Domain Command Struct:
type WorkspaceCommandAddMember struct {
WorkspaceUuid string `json:"workspaceUuid"`
IdentityUuid string `json:"identityUuid"`
GroupUuids []string `json:"groupUuids,omitempty"`
}Domain Command Handling Method:
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:
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:
func (cs *commandHandler) WorkspaceCommandAddWorkspaceMember(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandAddWorkspaceMember) ([]comby.Event, error)WorkspaceCommandCreate
Domain Command Struct:
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:
func (cs *commandHandler) WorkspaceCommandCreate(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandCreate) ([]comby.Event, error)WorkspaceCommandRemove
Domain Command Struct:
type WorkspaceCommandRemove struct {
WorkspaceUuid string `json:"workspaceUuid"`
}Domain Command Handling Method:
func (cs *commandHandler) WorkspaceCommandRemove(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemove) ([]comby.Event, error)WorkspaceCommandRemoveGroup
Domain Command Struct:
type WorkspaceCommandRemoveGroup struct {
WorkspaceUuid string `json:"workspaceUuid"`
GroupUuid string `json:"groupUuid"`
}Domain Command Handling Method:
func (cs *commandHandler) WorkspaceCommandRemoveGroup(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemoveGroup) ([]comby.Event, error)WorkspaceCommandRemoveMember
Domain Command Struct:
type WorkspaceCommandRemoveMember struct {
WorkspaceUuid string `json:"workspaceUuid"`
IdentityUuid string `json:"identityUuid"`
}Domain Command Handling Method:
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:
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:
func (cs *commandHandler) WorkspaceCommandRemoveWorkspaceMember(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandRemoveWorkspaceMember) ([]comby.Event, error)WorkspaceCommandUpdate
Domain Command Struct:
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:
func (cs *commandHandler) WorkspaceCommandUpdate(ctx context.Context, cmd comby.Command, domainCmd *WorkspaceCommandUpdate) ([]comby.Event, error)WorkspaceCommandUpdateGroup
Domain Command Struct:
type WorkspaceCommandUpdateGroup struct {
WorkspaceUuid string `json:"workspaceUuid"`
GroupUuid string `json:"groupUuid"`
Permissions []string `json:"permissions"`
PatchedFields []string `json:"patchedFields"`
}Domain Command Handling Method:
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:
type WorkspaceQueryGet struct {
WorkspaceUuid string `json:"workspaceUuid"`
IncludeHistory bool `json:"includeHistory,omitempty"`
}Domain Query Handling Method:
func (qs *queryHandler) WorkspaceQueryGet(ctx context.Context, qry comby.Query, domainQry *WorkspaceQueryGet) (*WorkspaceQueryItemResponse, error)WorkspaceQueryList
Domain Query Struct:
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:
func (qs *queryHandler) WorkspaceQueryList(ctx context.Context, qry comby.Query, domainQry *WorkspaceQueryList) (*WorkspaceQueryListResponse, error)WorkspaceQueryListByIdentity
Domain Query Struct:
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:
func (qs *queryHandler) WorkspaceQueryListByIdentity(ctx context.Context, qry comby.Query, domainQry *WorkspaceQueryListByIdentity) (*WorkspaceQueryListResponse, error)WorkspaceQueryItemResponse
type WorkspaceQueryItemResponse struct {
Item *readmodel.WorkspaceModel `json:"item,omitempty"`
}WorkspaceQueryListResponse
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
- WorkspaceMemberAddedEvent
- WorkspaceWorkspaceMemberAddedEvent
- WorkspaceCreatedEvent
- WorkspaceRemovedEvent
- WorkspaceGroupRemovedEvent
- WorkspaceMemberRemovedEvent
- WorkspaceWorkspaceMemberRemovedEvent
- WorkspaceUpdatedEvent
- WorkspaceGroupUpdatedEvent
WorkspaceGroupAddedEvent
Domain Event Struct:
type WorkspaceGroupAddedEvent struct {
GroupUuid string `json:"groupUuid"`
Name string `json:"name"`
PermissionNames []string `json:"permissionNames"`
}Domain Event Handling Method:
func (agg *Workspace) WorkspaceGroupAddedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceGroupAddedEvent) (error)WorkspaceMemberAddedEvent
Domain Event Struct:
type WorkspaceMemberAddedEvent struct {
IdentityUuid string `json:"identityUuid"`
GroupUuids []string `json:"groupUuids"`
}Domain Event Handling Method:
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:
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.
func (agg *Workspace) WorkspaceWorkspaceMemberAddedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceWorkspaceMemberAddedEvent) (error)WorkspaceCreatedEvent
Domain Event Struct:
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:
func (agg *Workspace) WorkspaceCreatedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceCreatedEvent) (error)WorkspaceRemovedEvent
Domain Event Struct:
type WorkspaceRemovedEvent struct {
Reason string `json:"reason,omitempty"`
}Domain Event Handling Method:
func (agg *Workspace) WorkspaceRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceRemovedEvent) (error)WorkspaceGroupRemovedEvent
Domain Event Struct:
type WorkspaceGroupRemovedEvent struct {
GroupUuid string `json:"groupUuid"`
}Domain Event Handling Method:
func (agg *Workspace) WorkspaceGroupRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceGroupRemovedEvent) (error)WorkspaceMemberRemovedEvent
Domain Event Struct:
type WorkspaceMemberRemovedEvent struct {
IdentityUuid string `json:"identityUuid"`
}Domain Event Handling Method:
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:
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.
func (agg *Workspace) WorkspaceWorkspaceMemberRemovedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceWorkspaceMemberRemovedEvent) (error)WorkspaceUpdatedEvent
Domain Event Struct:
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:
func (agg *Workspace) WorkspaceUpdatedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceUpdatedEvent) (error)WorkspaceGroupUpdatedEvent
Domain Event Struct:
type WorkspaceGroupUpdatedEvent struct {
GroupUuid string `json:"groupUuid"`
PermissionNames []string `json:"permissionNames"`
}Domain Event Handling Method:
func (agg *Workspace) WorkspaceGroupUpdatedEvent(ctx context.Context, evt comby.Event, domainEvt *WorkspaceGroupUpdatedEvent) (error)Aggregate
Aggregate Struct:
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
- AddMember
- AddWorkspaceMember
- Create
- Remove
- RemoveGroup
- RemoveMember
- RemoveWorkspaceMember
- Update
- UpdateGroup
AddGroup
func (agg *Workspace) AddGroup(opts ) (error)AddMember
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.
func (agg *Workspace) AddWorkspaceMember(opts ) (error)Create
func (agg *Workspace) Create(opts ) (error)Remove
func (agg *Workspace) Remove(opts ) (error)RemoveGroup
func (agg *Workspace) RemoveGroup(opts ) (error)RemoveMember
func (agg *Workspace) RemoveMember(opts ) (error)RemoveWorkspaceMember
RemoveWorkspaceMember removes a workspace from the members of this workspace.
func (agg *Workspace) RemoveWorkspaceMember(opts ) (error)Update
func (agg *Workspace) Update(opts ) (error)UpdateGroup
func (agg *Workspace) UpdateGroup(opts ) (error)Event Handlers
WorkspaceReadmodel
| Domain Event | Method |
|---|---|
aggregate.WorkspaceCreatedEvent | WorkspaceCreatedEvent |
aggregate.WorkspaceUpdatedEvent | WorkspaceUpdatedEvent |
aggregate.WorkspaceRemovedEvent | WorkspaceRemovedEvent |
aggregate.WorkspaceMemberAddedEvent | WorkspaceMemberAddedEvent |
aggregate.WorkspaceMemberRemovedEvent | WorkspaceMemberRemovedEvent |
aggregate.WorkspaceGroupAddedEvent | WorkspaceGroupAddedEvent |
aggregate.WorkspaceGroupUpdatedEvent | WorkspaceGroupUpdatedEvent |
aggregate.WorkspaceGroupRemovedEvent | WorkspaceGroupRemovedEvent |
aggregate.WorkspaceWorkspaceMemberAddedEvent | WorkspaceWorkspaceMemberAddedEvent |
aggregate.WorkspaceWorkspaceMemberRemovedEvent | WorkspaceWorkspaceMemberRemovedEvent |
Custom Permissions
| Name | Type | Comment |
|---|---|---|
| WorkspaceQueryListByIdentity | Query | List workspaces by identityUuid |