Organization Model
Q-Framework supports data access control based on hierarchical organizational structures. Organization-scoped data isolation is applied automatically through declarations alone — no manual WHERE clauses needed.
Organizational Hierarchy
Headquarters (ROOT)
├── Seoul HQ (BRANCH)
│ ├── Sales Team 1 (TEAM)
│ ├── Sales Team 2 (TEAM)
│ └── Support Team (TEAM)
└── Busan Branch (BRANCH)
├── Sales Team (TEAM)
└── Support Team (TEAM)Each organization has a unique ID and is structured in parent-child hierarchical relationships.
Organization Visibility Scope
Determines which organizational data a user can access.
Core Components
| Component | Description |
|---|---|
| Anchor | The reference organization for data access |
| Include Rule | How far from the anchor to include |
| Depth Limit | Maximum depth of the included scope |
Include Rule Options
| Rule | Description | Example |
|---|---|---|
SELF | Only the anchor organization | Only the user's own team data |
CHILDREN | Anchor plus subordinate organizations | Own team and all sub-teams |
ROOT_TO_SELF | Root down to the anchor | All ancestors from root to own team |
Organization Policy Declaration
@QfEntity(
appKey = "app",
name = @QfI18n(defaultMessage = "Sales Record", texts = { @QfI18nText(locale = "ko", message = "영업 실적") }),
organizationPolicy = @QfOrganizationPolicy(enabled = true)
)
public class SalesRecordEntity {
@QfDisplayLabel(text = "Amount")
@QfListAttribute(sortable = true)
private Long amount;
@QfDisplayLabel(text = "Sales Date")
@QfListAttribute(sortable = true)
private LocalDate salesDate;
}Organization Visibility Policy Configuration
Configure the organization visibility policy in application.yml.
qf:
organization:
visibility:
anchor: SELF # anchor: the user's own organization
include-rule: CHILDREN # scope: self + subordinate organizations
depth-limit: 2 # up to 2 levels belowPolicy Behavior Examples
Scenario: A user belonging to Seoul HQ queries data
Organization tree:
Headquarters
└── Seoul HQ ← current user
├── Sales Team 1
├── Sales Team 2
└── Support Team| anchor | include-rule | depth-limit | Accessible scope |
|---|---|---|---|
| SELF | SELF | — | Seoul HQ data only |
| SELF | CHILDREN | 1 | Seoul HQ + 3 direct teams |
| SELF | CHILDREN | unlimited | Seoul HQ + all subordinates |
| SELF | ROOT_TO_SELF | — | Headquarters + Seoul HQ |
Runtime Application
Organization scope queries are generated automatically. Developers never need to write them manually.
-- Auto-generated condition by Q-Framework
-- (anchor=SELF, include-rule=CHILDREN, depth-limit=2)
WHERE organization_id IN (
SELECT id FROM organization
WHERE id = :currentUserOrgId
OR parent_id = :currentUserOrgId
OR (parent_id IN (
SELECT id FROM organization WHERE parent_id = :currentUserOrgId
))
)Organization Model in SaaS Environments
Q-Framework's organization model naturally supports multi-tenant SaaS environments.
Tenant = Root Organization
Tenant A (Root) Tenant B (Root)
└── Seoul Office └── Headquarters
└── Sales Team └── Dev TeamEach tenant becomes a root organization. Setting anchor=SELF, include-rule=CHILDREN automatically isolates one tenant's data from another.
Comparison with Traditional Multi-Tenant Models
| Aspect | Traditional Multi-Tenant | Q-Framework Organization Model |
|---|---|---|
| Isolation mechanism | Hardcoded tenant_id column | Hierarchical org visibility scope |
| Internal hierarchy support | Requires separate implementation | Built-in |
| Permission granularity | Complex RBAC implementation | Org hierarchy + Privilege |
| Declaration style | Code-level implementation | Annotation declaration |
Providing Organization Data (SPI)
Organization data is provided through the QfOrganizationProvider SPI.
@Component
public class MyOrganizationProvider implements QfOrganizationProvider {
@Override
public List<QfOrganization> getOrganizationHierarchy(String rootOrgId) {
// return organization hierarchy data
return organizationRepository.findHierarchyByRoot(rootOrgId)
.stream()
.map(org -> QfOrganization.builder()
.id(org.getId())
.parentId(org.getParentId())
.name(org.getName())
.depth(org.getDepth())
.build())
.collect(Collectors.toList());
}
}Next Steps
- Internationalization — Multi-language support
- SPI Extensions — Custom organization provider