freetool-openfga-hexagonal-architecture
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFreetool OpenFGA Hexagonal Architecture
Freetool OpenFGA 六边形架构
Overview
概述
Use this skill to produce repo-accurate guidance for OpenFGA in Freetool: where it is implemented, where it is not, and how to extend it without violating architecture boundaries.
使用本技能可以为Freetool中的OpenFGA实现提供符合仓库规范的指导:包括该在哪里实现、不该在哪里实现,以及如何在不违反架构边界的前提下扩展能力。
Workflow
工作流
- Read boundary-defining files first.
- Map each OpenFGA concern to API/Application/Infrastructure/Domain.
- Explain both positive placement ("implement here") and negative placement ("do not implement here").
- Include short F# snippets from the codebase that demonstrate best practice.
- Call out practical caveats and extension checklist.
- 优先读取边界定义文件
- 将每个OpenFGA相关需求映射到API/应用层/基础设施层/领域层
- 同时说明正向放置规则("此处实现")和反向禁止规则("此处禁止实现")
- 引入代码库中的简短F#代码片段作为最佳实践示例
- 列出实操注意事项和扩展检查清单
Read First
优先读取文件
src/Freetool.Application/src/Interfaces/IAuthorizationService.fssrc/Freetool.Infrastructure/src/Services/OpenFgaService.fssrc/Freetool.Api/src/Program.fssrc/Freetool.Api/src/Controllers/AuthenticatedControllerBase.fssrc/Freetool.Api/src/Controllers/SpaceController.fssrc/Freetool.Application/src/Handlers/SpaceHandler.fssrc/Freetool.Api/src/Services/IdentityProvisioningService.fs
src/Freetool.Application/src/Interfaces/IAuthorizationService.fssrc/Freetool.Infrastructure/src/Services/OpenFgaService.fssrc/Freetool.Api/src/Program.fssrc/Freetool.Api/src/Controllers/AuthenticatedControllerBase.fssrc/Freetool.Api/src/Controllers/SpaceController.fssrc/Freetool.Application/src/Handlers/SpaceHandler.fssrc/Freetool.Api/src/Services/IdentityProvisioningService.fs
Layer Mapping (Best Practice)
层级映射(最佳实践)
- : Middleware authenticates and sets
API layer (inbound adapters); controllers enforce endpoint-level authorization checks and orchestration.UserId - : Defines authorization port and typed auth language (
Application layer (ports + use cases),IAuthorizationService,AuthSubject,AuthRelation); handlers orchestrate business use cases that need permission reads/writes.AuthObject - : Implements OpenFGA SDK calls in
Infrastructure layer (outbound adapter).OpenFgaService - : Contains zero OpenFGA/SDK knowledge; no external auth client details.
Domain layer
- :中间件完成身份认证并设置
API层(入站适配器);控制器负责执行接口级权限校验和流程编排UserId - :定义授权端口和类型化权限语言(
应用层(端口+用例)、IAuthorizationService、AuthSubject、AuthRelation);handler编排需要权限读写的业务用例AuthObject - :在
基础设施层(出站适配器)中实现OpenFGA SDK调用逻辑OpenFgaService - :完全不感知OpenFGA/SDK相关内容,不包含任何外部权限客户端细节
领域层
Implement Here vs Do Not Implement Here
可实现区域 vs 禁止实现区域
Implement Here
可实现区域
- Define authorization operations in (
IAuthorizationService).src/Freetool.Application/src/Interfaces/IAuthorizationService.fs - Implement all OpenFGA SDK interactions in (
OpenFgaService).src/Freetool.Infrastructure/src/Services/OpenFgaService.fs - Register the adapter in DI and initialize store/model in .
Program.fs - Perform endpoint authorization checks in controllers (for request-scoped decisions).
- Use application handlers for permission-diff orchestration and event coupling (for use-case-level permission changes).
- 在(
IAuthorizationService)中定义授权操作src/Freetool.Application/src/Interfaces/IAuthorizationService.fs - 在(
OpenFgaService)中实现所有OpenFGA SDK交互逻辑src/Freetool.Infrastructure/src/Services/OpenFgaService.fs - 在中注册适配器并初始化存储/模型
Program.fs - 在控制器中执行接口权限校验(用于请求级别的权限判断)
- 在应用层handler中处理权限差异编排和事件耦合(用于用例级别的权限变更)
Do Not Implement Here
禁止实现区域
- Do not call OpenFGA SDK directly from controllers, handlers, or domain.
- Do not put OpenFGA tuple-string literals throughout API/domain logic; use typed auth unions and helpers.
- Do not place authorization business policy inside domain entities.
- Do not make Domain reference or infrastructure types.
IAuthorizationService
- 禁止在控制器、handler或领域层直接调用OpenFGA SDK
- 禁止在API/领域逻辑中随处散落OpenFGA元组字符串字面量,使用类型化权限联合类型和辅助方法
- 禁止将授权业务规则放在领域实体中
- 禁止让领域层引用或基础设施层类型
IAuthorizationService
Code Samples
代码示例
1) Application Port with Typed Auth Language
1) 带类型化权限语言的应用层端口
src/Freetool.Application/src/Interfaces/IAuthorizationService.fsfsharp
type IAuthorizationService =
abstract member CreateRelationshipsAsync: RelationshipTuple list -> Task<unit>
abstract member CheckPermissionAsync:
subject: AuthSubject -> relation: AuthRelation -> object: AuthObject -> Task<bool>src/Freetool.Application/src/Interfaces/IAuthorizationService.fsfsharp
type IAuthorizationService =
abstract member CreateRelationshipsAsync: RelationshipTuple list -> Task<unit>
abstract member CheckPermissionAsync:
subject: AuthSubject -> relation: AuthRelation -> object: AuthObject -> Task<bool>2) Infrastructure Adapter Owns OpenFGA SDK
2) 基础设施层适配器托管OpenFGA SDK逻辑
src/Freetool.Infrastructure/src/Services/OpenFgaService.fsfsharp
open OpenFga.Sdk.Client
type OpenFgaService(apiUrl: string, logger: ILogger<OpenFgaService>, ?storeId: string) =
interface IAuthorizationService with
member _.CheckPermissionAsync(subject, relation, object) : Task<bool> =
task {
use client = createClient ()
let body =
ClientCheckRequest(
User = AuthTypes.subjectToString subject,
Relation = AuthTypes.relationToString relation,
Object = AuthTypes.objectToString object
)
let! response = client.Check(body)
return response.Allowed.GetValueOrDefault(false)
}src/Freetool.Infrastructure/src/Services/OpenFgaService.fsfsharp
open OpenFga.Sdk.Client
type OpenFGApiService(apiUrl: string, logger: ILogger<OpenFgaService>, ?storeId: string) =
interface IAuthorizationService with
member _.CheckPermissionAsync(subject, relation, object) : Task<bool> =
task {
use client = createClient ()
let body =
ClientCheckRequest(
User = AuthTypes.subjectToString subject,
Relation = AuthTypes.relationToString relation,
Object = AuthTypes.objectToString object
)
let! response = client.Check(body)
return response.Allowed.GetValueOrDefault(false)
}3) DI Wiring Keeps API Depending on Port
3) DI 注入确保API层仅依赖端口
src/Freetool.Api/src/Program.fsfsharp
builder.Services.AddScoped<IAuthorizationService>(fun serviceProvider ->
let loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>()
let logger = loggerFactory.CreateLogger<OpenFgaService>()
OpenFgaService(openFgaApiUrl, logger, actualStoreId) :> IAuthorizationService)
|> ignoresrc/Freetool.Api/src/Program.fsfsharp
builder.Services.AddScoped<IAuthorizationService>(fun serviceProvider ->
let loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>()
let logger = loggerFactory.CreateLogger<OpenFgaService>()
OpenFgaService(openFgaApiUrl, logger, actualStoreId) :> IAuthorizationService)
|> ignore4) Controller Performs Endpoint Permission Check via Port
4) 控制器通过端口执行接口权限校验
src/Freetool.Api/src/Controllers/AppController.fsfsharp
let! hasPermission =
authorizationService.CheckPermissionAsync
(User(userId.Value.ToString()))
permission
(SpaceObject(spaceId.Value.ToString()))src/Freetool.Api/src/Controllers/AppController.fsfsharp
let! hasPermission =
authorizationService.CheckPermissionAsync
(User(userId.Value.ToString()))
permission
(SpaceObject(spaceId.Value.ToString()))5) Handler Performs Permission Diff + Atomic Tuple Update
5) Handler 执行权限差异对比 + 原子元组更新
src/Freetool.Application/src/Handlers/SpaceHandler.fsfsharp
if not (List.isEmpty tuplesToAdd) || not (List.isEmpty tuplesToRemove) then
do!
authService.UpdateRelationshipsAsync
{ TuplesToAdd = tuplesToAdd
TuplesToRemove = tuplesToRemove }src/Freetool.Application/src/Handlers/SpaceHandler.fsfsharp
if not (List.isEmpty tuplesToAdd) || not (List.isEmpty tuplesToRemove) then
do!
authService.UpdateRelationshipsAsync
{ TuplesToAdd = tuplesToAdd
TuplesToRemove = tuplesToRemove }Runtime Flow (Request to Decision)
运行时流程(从请求到权限决策)
- Middleware authenticates user and sets .
HttpContext.Items["UserId"] - reads
AuthenticatedControllerBase.CurrentUserId - Controller calls .
IAuthorizationService.CheckPermissionAsync - converts typed values to OpenFGA tuple strings and calls SDK.
OpenFgaService - Controller/handler continues or returns .
403
- 中间件完成用户身份认证并设置
HttpContext.Items["UserId"] - 读取
AuthenticatedControllerBaseCurrentUserId - 控制器调用
IAuthorizationService.CheckPermissionAsync - 将类型化值转换为OpenFGA元组字符串并调用SDK
OpenFgaService - 控制器/handler继续执行业务逻辑或返回
403
Extension Checklist
扩展检查清单
When adding new authorization behavior:
- Add/adjust or typed auth models in
AuthRelation.IAuthorizationService.fs - Update relation-to-string mapping in .
AuthTypes - Update OpenFGA model definition in .
OpenFgaService.WriteAuthorizationModelAsync - Add use-case checks in controller and/or handler via .
IAuthorizationService - Add integration tests for allowed/denied paths and tuple-write behavior.
新增授权逻辑时:
- 在中新增/调整
IAuthorizationService.fs或类型化权限模型AuthRelation - 更新中的权限关系到字符串的映射逻辑
AuthTypes - 在中更新OpenFGA模型定义
OpenFgaService.WriteAuthorizationModelAsync - 通过在控制器和/或handler中新增用例级校验
IAuthorizationService - 为允许/拒绝路径和元组写入行为新增集成测试
Anti-Patterns to Flag in Review
代码评审中需要标记的反模式
- outside infrastructure.
open OpenFga.Sdk.* - Raw relation strings like scattered in controllers/handlers.
"create_app" - Domain entities making auth service calls.
- Controller bypassing typed API.
AuthSubject/AuthRelation/AuthObject
- 在基础设施层以外引入
open OpenFga.Sdk.* - 控制器/handler中散落这类原始权限关系字符串
"create_app" - 领域实体调用权限服务
- 控制器绕过类型化API直接使用原始值
AuthSubject/AuthRelation/AuthObject
Fast Verification Commands
快速校验命令
bash
rg -n "open OpenFga\\.Sdk|OpenFgaService|IAuthorizationService|CheckPermissionAsync|CreateRelationshipsAsync|UpdateRelationshipsAsync" src --glob '*.fs'bash
rg -n "CurrentUserId|HttpContext\\.Items\\[\\\"UserId\\\"\\]|UseMiddleware<IapAuthMiddleware>|UseMiddleware<DevAuthMiddleware>" src/Freetool.Api/src --glob '*.fs'bash
rg -n "open OpenFga\\.Sdk|OpenFgaService|IAuthorizationService|CheckPermissionAsync|CreateRelationshipsAsync|UpdateRelationshipsAsync" src --glob '*.fs'bash
rg -n "CurrentUserId|HttpContext\\.Items\\[\\\"UserId\\\"\\]|UseMiddleware<IapAuthMiddleware>|UseMiddleware<DevAuthMiddleware>" src/Freetool.Api/src --glob '*.fs'