Skip to content

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

ComponentDescription
AnchorThe reference organization for data access
Include RuleHow far from the anchor to include
Depth LimitMaximum depth of the included scope

Include Rule Options

RuleDescriptionExample
SELFOnly the anchor organizationOnly the user's own team data
CHILDRENAnchor plus subordinate organizationsOwn team and all sub-teams
ROOT_TO_SELFRoot down to the anchorAll ancestors from root to own team

Organization Policy Declaration

java
@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.

yaml
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 below

Policy 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
anchorinclude-ruledepth-limitAccessible scope
SELFSELFSeoul HQ data only
SELFCHILDREN1Seoul HQ + 3 direct teams
SELFCHILDRENunlimitedSeoul HQ + all subordinates
SELFROOT_TO_SELFHeadquarters + Seoul HQ

Runtime Application

Organization scope queries are generated automatically. Developers never need to write them manually.

sql
-- 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 Team

Each 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

AspectTraditional Multi-TenantQ-Framework Organization Model
Isolation mechanismHardcoded tenant_id columnHierarchical org visibility scope
Internal hierarchy supportRequires separate implementationBuilt-in
Permission granularityComplex RBAC implementationOrg hierarchy + Privilege
Declaration styleCode-level implementationAnnotation declaration

Providing Organization Data (SPI)

Organization data is provided through the QfOrganizationProvider SPI.

java
@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

Released under the Apache 2.0 License.