디렉티브를 통한 IFTTT
Gato GraphQL은 디렉티브를 통해 IFTTT(If This Then That) 전략을 구현하는 기능을 제공합니다. 이 디렉티브들은 쿼리 내에 특정 필드나 디렉티브가 존재할 때 동적으로 쿼리에 추가됩니다.
일반적으로 IFTTT는 지정된 이벤트가 발생할 때마다 액션을 실행하는 규칙입니다. 여기서는 이벤트와 액션의 쌍이 다음과 같습니다.
- 「쿼리에 필드 X가 발견된 경우」→「필드 X에 디렉티브 Y를 첨부한다」
- 「쿼리에 디렉티브 Z가 발견된 경우」→「디렉티브 Z의 전후에 디렉티브 Y를 실행한다」
스키마에 IFTTT 디렉티브를 동적으로 추가하는 것은 재귀적인 프로세스입니다. 해당 디렉티브 자체에도 고유한 IFTTT 디렉티브 세트를 설정할 수 있으며, 이것들도 디렉티브 체인에 추가됩니다.
사용 사례
내부적으로 Gato GraphQL의 클라이언트들은 이 메커니즘을 사용하여 GraphQL 스키마를 구성합니다.
예를 들어, Access Control을 통해 오퍼레이션, 필드, 디렉티브에 적용할 접근 제어 규칙을 선택할 수 있습니다. IFTTT를 사용함으로써 이러한 규칙들이 GraphQL 스키마의 해당 요소에 적용됩니다.

일반적으로 다음과 같은 사용 사례들이 있습니다.
필드별 캐시 제어 max-age 정의
모든 필드에 @CacheControl 디렉티브를 첨부하고 maxAge 파라미터 값을 커스터마이징합니다. Post의 url 필드에는 1년, title 필드에는 1시간을 설정합니다.
접근 제어 설정
User 타입의 email 필드에 @validateDoesLoggedInUserHaveAnyRole 디렉티브를 첨부하여, 관리자만 사용자 이메일을 쿼리할 수 있도록 합니다.
접근 제어와 캐시 제어의 동기화
디렉티브를 체인으로 연결함으로써, 사용자가 필드나 디렉티브에 접근할 수 있는지 검증할 때 응답이 캐시되지 않도록 보장할 수 있습니다. 예를 들어 다음과 같이 설정합니다.
me필드에@validateIsUserLoggedIn디렉티브를 첨부한다@validateIsUserLoggedIn디렉티브에maxAge인수 값을0으로 설정한@CacheControl디렉티브를 첨부한다
보안 강화
@translate 디렉티브에 @validateIsUserLoggedIn 디렉티브를 첨부하여, 악의적인 행위자가 GraphQL 서비스에 쿼리를 실행하여 서버를 다운시키거나 요금을 급증시키는 것을 방지합니다(이 경우 @translate는 Google Translate를 기반으로 하며 서비스 사용에 비용이 발생합니다).
동작 방식
IFTTT를 통해 어떻게 디렉티브를 스키마에 추가할까요? 예를 들어, 커스텀 디렉티브 @authorize(role: String!)를 만들어 myPosts 필드를 실행하는 사용자가 예상 역할인 author를 가지고 있는지 검증하고, 그렇지 않으면 오류를 표시하고자 한다고 가정합니다.
SDL을 사용하여 스키마를 만든 경우, 다음과 같이 됩니다.
directive @authorize(role: String!) on FIELD_DEFINITION
type User {
myPosts: [Post] @authorize(role: "author")
}IFTTT 규칙은 위의 SDL이 선언하는 것과 동일한 의도를 정의합니다. 즉, 필드 myPosts가 요청될 때마다 해당 필드에 디렉티브 @authorize(role: "author")를 실행합니다. 쿼리에서 필드 myPosts가 발견되면, 엔진은 실행 가능한 쿼리의 해당 필드에 자동으로 @authorize(role: 'author')를 첨부합니다.
IFTTT 규칙은 필드뿐만 아니라 디렉티브를 만났을 때도 실행될 수 있습니다. 예를 들어, 「쿼리에 디렉티브 @translate가 발견된 경우, 해당 필드에 디렉티브 @cache(time: 3600)를 실행한다」는 규칙을 설정할 수 있습니다.
쿼리에 IFTTT 디렉티브를 추가하는 것은 재귀적인 프로세스입니다. 새로운 이벤트가 실행되어 IFTTT 규칙에 의해 처리되고, 다른 디렉티브들이 쿼리에 추가될 수 있으며, 이 과정이 계속됩니다.
예를 들어, 「디렉티브 @cache가 발견된 경우 디렉티브 @log를 실행한다」는 규칙은 필드 실행에 관한 항목을 기록하고, 새로 추가된 이 디렉티브에 관한 새로운 이벤트를 실행합니다.
PHP 코드를 통한 설정
User 타입에는 roles와 capabilities 필드가 있으며, 이것들은 민감한 정보로 간주될 수 있으므로 일반 사용자는 접근할 수 없도록 해야 합니다.
따라서 이 두 필드에 @validateDoesLoggedInUserHaveAnyRole 디렉티브를 첨부하여, 환경 변수를 통해 설정된 특정 역할을 가진 사용자만 접근할 수 있도록 구성할 수 있습니다. 설정은 CompilerPass를 통해 제공됩니다.
$accessControlManagerDefinition = $containerBuilderWrapper->getDefinition(AccessControlManagerInterface::class);
if ($roles = Environment::anyRoleLoggedInUserMustHaveToAccessRolesFields()) {
$accessControlManagerDefinition->addMethodCall(
'addEntriesForFields',
[
UserRolesAccessControlGroups::ROLES,
[
[RootObjectTypeResolver::class, 'roles', $roles],
[UserObjectTypeResolver::class, 'roles', $roles],
[RootObjectTypeResolver::class, 'capabilities', $roles],
[UserObjectTypeResolver::class, 'capabilities', $roles],
]
]
);
}
if ($capabilities = Environment::anyCapabilityLoggedInUserMustHaveToAccessRolesFields()) {
$accessControlManagerDefinition->addMethodCall(
'addEntriesForFields',
[
UserCapabilitiesAccessControlGroups::CAPABILITIES,
[
[RootObjectTypeResolver::class, 'roles', $capabilities],
[UserObjectTypeResolver::class, 'roles', $capabilities],
[RootObjectTypeResolver::class, 'capabilities', $capabilities],
[UserObjectTypeResolver::class, 'capabilities', $capabilities],
]
]
);
}쿼리를 실행할 때, 로그인하지 않은 사용자나 필요한 역할을 가지지 않은 사용자는 이러한 필드에 대한 접근이 허용되지 않습니다.