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 = "영업 실적") }),
    ownerPolicy = @QfOwnerPolicy(enabled = true, attribute = "orgId")
)
public class SalesRecordEntity {

    @QfI18n(defaultMessage = "Amount")
    @QfListAttribute(sortable = true)
    private Long amount;

    @QfI18n(defaultMessage = "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
SELFSELF—Seoul HQ data only
SELFCHILDREN1Seoul HQ + 3 direct teams
SELFCHILDRENunlimitedSeoul HQ + all subordinates
SELFROOT_TO_SELF—Headquarters + 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> getAllOrganizations() {
        return organizationRepository.findAll()
            .stream()
            .map(org -> QfOrganization.builder()
                .id(org.getId())
                .parentId(org.getParentId())
                .name(org.getName())
                .depth(org.getDepth())
                .build())
            .collect(Collectors.toList());
    }

    @Override
    public List<QfOrganization> getOrganizations(Object userId) {
        return organizationRepository.findByUserId(userId)
            .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.