AI 던전 게임
모듈 1: 모노레포 설정
섹션 제목: “모듈 1: 모노레포 설정”먼저 새로운 모노레포를 생성합니다. 원하는 디렉토리 내에서 다음 명령어를 실행하세요:
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=pnpm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=yarn --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=npm --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
npx create-nx-workspace@~21.4.1 dungeon-adventure --pm=bun --preset=@aws/nx-plugin --iacProvider=CDK --ci=skip
이 명령은 dungeon-adventure
디렉토리 내에 NX 모노레포를 설정하며, 이후 vscode에서 열 수 있습니다. 결과는 다음과 같아야 합니다:
디렉터리.nx/
- …
디렉터리.vscode/
- …
디렉터리node_modules/
- …
디렉터리packages/ 여기에 하위 프로젝트들이 위치합니다
- …
- .gitignore
- .npmrc
- .prettierignore
- .prettierrc
- nx.json Nx CLI 및 모노레포 기본값을 구성합니다
- package.json 모든 노드 의존성이 정의됩니다
- pnpm-lock.yaml 또는 사용하는 패키지 매니저에 따라 bun.lock, yarn.lock, package-lock.json
- pnpm-workspace.yaml pnpm 사용 시
- README.md
- tsconfig.base.json 모든 노드 기반 하위 프로젝트가 이를 확장합니다
- tsconfig.json
- aws-nx-plugin.config.mts AWS용 Nx 플러그인 구성
이제 @aws/nx-plugin
을 사용하여 다양한 하위 프로젝트를 생성할 준비가 되었습니다.
게임 API
섹션 제목: “게임 API”먼저 Game API를 생성해 보겠습니다. 다음 단계에 따라 GameApi
라는 tRPC API를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#trpc-api
- 필수 매개변수 입력
- name: GameApi
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --name=GameApi --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타난 것을 볼 수 있습니다.
ts#trpc-api 업데이트된 파일
다음은 ts#trpc-api
제너레이터에 의해 생성된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리app/ 애플리케이션 특정 CDK 구문
디렉터리apis/
- game-api.ts tRPC API를 생성하는 CDK 구문
- index.ts
- …
- index.ts
디렉터리core/ 일반적인 CDK 구문
디렉터리api/
- rest-api.ts API Gateway Rest API를 위한 기본 CDK 구문
- trpc-utils.ts tRPC API CDK 구문 유틸리티
- utils.ts API 구문 유틸리티
- index.ts
- runtime-config.ts
- index.ts
- project.json
- …
디렉터리types/ 공유 유형
디렉터리src/
- index.ts
- runtime-config.ts CDK와 웹사이트 모두에서 사용되는 인터페이스 정의
- project.json
- …
디렉터리game-api/ tRPC API
디렉터리src/
디렉터리client/ 일반적으로 TS 머신 간 호출에 사용되는 클라이언트
- index.ts
- sigv4.ts
디렉터리middleware/ Powertools 계측
- error.ts
- index.ts
- logger.ts
- metrics.ts
- tracer.ts
디렉터리schema/ API 입력 및 출력 정의
- echo.ts
디렉터리procedures/ API 프로시저/라우트 구현
- echo.ts
- index.ts
- init.ts 컨텍스트 및 미들웨어 설정
- local-server.ts tRPC 서버를 로컬에서 실행할 때 사용
- router.ts 모든 프로시저를 정의하는 람다 핸들러 진입점
- project.json
- …
- eslint.config.mjs
- vitest.workspace.ts
몇 가지 주요 파일을 살펴보겠습니다:
import { awsLambdaRequestHandler, CreateAWSLambdaContextOptions,} from '@trpc/server/adapters/aws-lambda';import { echo } from './procedures/echo.js';import { t } from './init.js';import { APIGatewayProxyEvent } from 'aws-lambda';
export const router = t.router;
export const appRouter = router({ echo,});
export const handler = awsLambdaRequestHandler({ router: appRouter, createContext: ( ctx: CreateAWSLambdaContextOptions<APIGatewayProxyEvent>, ) => ctx, responseMeta: () => ({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': '*', }, }),});
export type AppRouter = typeof appRouter;
라우터는 tRPC API의 진입점을 정의하며 모든 API 메서드를 선언하는 곳입니다. 위에서 볼 수 있듯이 echo
메서드가 있으며 그 구현은 ./procedures/echo.ts
파일에 있습니다.
import { publicProcedure } from '../init.js';import { EchoInputSchema, EchoOutputSchema,} from '../schema/echo.js';
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ result: opts.input.message }));
이 파일은 echo
메서드의 구현이며 입력 및 출력 데이터 구조를 선언하여 강력한 타입을 가집니다.
import { z } from 'zod';
export const EchoInputSchema = z.object({ message: z.string(),});
export type IEchoInput = z.TypeOf<typeof EchoInputSchema>;
export const EchoOutputSchema = z.object({ result: z.string(),});
export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
모든 tRPC 스키마 정의는 Zod를 사용하여 정의되며 z.TypeOf
구문을 통해 TypeScript 타입으로 내보내집니다.
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';import { AppRouter, appRouter } from ':dungeon-adventure/game-api';
// 모든 API 작업 이름을 위한 문자열 유니온 타입type Operations = Procedures<AppRouter>;
/** * GameApi 구문 생성 속성 * * @template TIntegrations - 작업 이름을 통합에 매핑하는 타입 */export interface GameApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * 작업 이름을 API Gateway 통합에 매핑 */ integrations: TIntegrations;}
/** * GameApi를 위한 AWS API Gateway REST API를 생성 및 구성하는 CDK 구문 * @template TIntegrations - 작업 이름을 통합에 매핑하는 타입 */export class GameApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 모든 작업에 대한 기본 통합 생성. 각 작업을 개별 람다 함수로 구현합니다. * * @param scope - CDK 구문 범위 * @returns 기본 람다 통합이 있는 IntegrationBuilder */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: routerToOperations(appRouter), defaultIntegrationOptions: { runtime: Runtime.NODEJS_LATEST, handler: 'index.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/game-api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `GameApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: GameApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'GameApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // 프로젝트가 배포된 계정의 AWS 자격 증명으로 API 호출을 허용합니다. // 필요한 경우 특정 주체(예: 역할 또는 사용자) 및 리소스(예: 어떤 주체가 어떤 API 경로를 호출할 수 있는지)를 정의하여 세분화된 접근 제어를 할 수 있습니다. new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // 브라우저가 인증 없이 사전 요청을 할 수 있도록 OPTIONS를 개방합니다 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: routerToOperations(appRouter), ...props, }); }}
이것은 GameApi를 정의하는 CDK 구문입니다. 보시다시피 defaultIntegrations
메서드를 제공하여 tRPC API의 각 프로시저에 대한 람다 함수를 자동으로 생성하며, 번들된 API 구현을 가리킵니다. 이는 cdk synth
시점에 번들링이 발생하지 않음을 의미합니다(NodeJsFunction 사용과 달리). 백엔드 프로젝트의 빌드 대상의 일부로 이미 번들링되었기 때문입니다.
스토리 API
섹션 제목: “스토리 API”이제 Story API를 생성해 보겠습니다. 다음 단계에 따라 StoryApi
라는 Fast API를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - py#fast-api
- 필수 매개변수 입력
- name: StoryApi
- moduleName: story_api
- 클릭
Generate
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:py#fast-api --name=StoryApi --moduleName=story_api --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타난 것을 볼 수 있습니다.
py#fast-api 업데이트된 파일
다음은 py#fast-api
제너레이터에 의해 생성된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리.venv/ 모노레포를 위한 단일 가상 환경
- …
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리app/ 애플리케이션 특정 CDK 구문
디렉터리apis/
- story-api.ts Fast API를 생성하는 CDK 구문
- index.ts 새 story-api를 내보내도록 업데이트됨
- project.json story_api에 빌드 종속성 추가로 업데이트됨
디렉터리types/ 공유 유형
디렉터리src/
- runtime-config.ts StoryApi 추가로 업데이트됨
디렉터리story_api/
디렉터리story_api/ Python 모듈
- init.py Powertools, FastAPI 및 미들웨어 설정
- main.py 모든 라우트를 포함하는 람다 진입점
디렉터리tests/
- …
- .python-version
- project.json
- pyproject.toml
- project.json
- .python-version 고정된 uv Python 버전
- pyproject.toml
- uv.lock
import { Construct } from 'constructs';import * as url from 'url';import { Code, Runtime, Function, FunctionProps, Tracing,} from 'aws-cdk-lib/aws-lambda';import { AuthorizationType, Cors, LambdaIntegration,} from 'aws-cdk-lib/aws-apigateway';import { Duration, Stack } from 'aws-cdk-lib';import { PolicyDocument, PolicyStatement, Effect, AccountPrincipal, AnyPrincipal,} from 'aws-cdk-lib/aws-iam';import { IntegrationBuilder, RestApiIntegration,} from '../../core/api/utils.js';import { RestApi } from '../../core/api/rest-api.js';import { OPERATION_DETAILS, Operations,} from '../../generated/story-api/metadata.gen.js';
/** * StoryApi 구문 생성 속성 * * @template TIntegrations - 작업 이름을 통합에 매핑하는 타입 */export interface StoryApiProps< TIntegrations extends Record<Operations, RestApiIntegration>,> { /** * 작업 이름을 API Gateway 통합에 매핑 */ integrations: TIntegrations;}
/** * StoryApi를 위한 AWS API Gateway REST API를 생성 및 구성하는 CDK 구문 * @template TIntegrations - 작업 이름을 통합에 매핑하는 타입 */export class StoryApi< TIntegrations extends Record<Operations, RestApiIntegration>,> extends RestApi<Operations, TIntegrations> { /** * 모든 작업에 대한 기본 통합 생성. 각 작업을 개별 람다 함수로 구현합니다. * * @param scope - CDK 구문 범위 * @returns 기본 람다 통합이 있는 IntegrationBuilder */ public static defaultIntegrations = (scope: Construct) => { return IntegrationBuilder.rest({ operations: OPERATION_DETAILS, defaultIntegrationOptions: { runtime: Runtime.PYTHON_3_12, handler: 'story_api.main.handler', code: Code.fromAsset( url.fileURLToPath( new URL( '../../../../../../dist/packages/story_api/bundle', import.meta.url, ), ), ), timeout: Duration.seconds(30), tracing: Tracing.ACTIVE, environment: { AWS_CONNECTION_REUSE_ENABLED: '1', }, } satisfies FunctionProps, buildDefaultIntegration: (op, props: FunctionProps) => { const handler = new Function(scope, `StoryApi${op}Handler`, props); return { handler, integration: new LambdaIntegration(handler) }; }, }); };
constructor( scope: Construct, id: string, props: StoryApiProps<TIntegrations>, ) { super(scope, id, { apiName: 'StoryApi', defaultMethodOptions: { authorizationType: AuthorizationType.IAM, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, }, policy: new PolicyDocument({ statements: [ // 프로젝트가 배포된 계정의 AWS 자격 증명으로 API 호출을 허용합니다. // 필요한 경우 특정 주체(예: 역할 또는 사용자) 및 리소스(예: 어떤 주체가 어떤 API 경로를 호출할 수 있는지)를 정의하여 세분화된 접근 제어를 할 수 있습니다. new PolicyStatement({ effect: Effect.ALLOW, principals: [new AccountPrincipal(Stack.of(scope).account)], actions: ['execute-api:Invoke'], resources: ['execute-api:/*'], }), // 브라우저가 인증 없이 사전 요청을 할 수 있도록 OPTIONS를 개방합니다 new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/OPTIONS/*'], }), ], }), operations: OPERATION_DETAILS, ...props, }); }}
이것은 StoryApi를 정의하는 CDK 구문입니다. 보시다시피 defaultIntegrations
메서드를 제공하여 FastAPI의 각 작업에 대한 람다 함수를 자동으로 생성하며, 번들된 API 구현을 가리킵니다. 이는 cdk synth
시점에 번들링이 발생하지 않음을 의미합니다(PythonFunction 사용과 달리). 백엔드 프로젝트의 빌드 대상의 일부로 이미 번들링되었기 때문입니다.
from .init import app, lambda_handler, tracer
handler = lambda_handler
@app.get("/")@tracer.capture_methoddef read_root(): return {"Hello": "World"}
이곳에 모든 API 메서드가 정의됩니다. 여기서는 GET /
라우트에 매핑된 read_root
메서드가 있습니다. Pydantic을 사용하여 메서드 입력 및 출력을 선언하여 타입 안전성을 보장할 수 있습니다.
게임 UI: 웹사이트
섹션 제목: “게임 UI: 웹사이트”이제 게임과 상호작용할 수 있는 UI를 생성해 보겠습니다. 다음 단계에 따라 GameUI
라는 웹사이트를 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#react-website
- 필수 매개변수 입력
- name: GameUI
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website --name=GameUI --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타난 것을 볼 수 있습니다.
ts#react-website 업데이트된 파일
다음은 ts#react-website
제너레이터에 의해 생성된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리app/ 애플리케이션 특정 CDK 구문
디렉터리static-websites/
- game-ui.ts Game UI를 생성하는 CDK 구문
디렉터리core/
- static-website.ts 일반 정적 웹사이트 구문
디렉터리game-ui/
디렉터리public/
- …
디렉터리src/
디렉터리components/
디렉터리AppLayout/
- index.ts 전체 페이지 레이아웃: 헤더, 푸터, 사이드바 등
- navitems.ts 사이드바 네비게이션 항목
디렉터리hooks/
- useAppLayout.tsx 알림, 페이지 스타일 등을 동적으로 설정할 수 있게 함
디렉터리routes/ @tanstack/react-router 파일 기반 라우트
- index.tsx 루트 ’/’ 페이지가 ‘/welcome’으로 리다이렉트
- __root.tsx 모든 페이지가 기본 컴포넌트로 사용
디렉터리welcome/
- index.tsx
- config.ts
- main.tsx React 진입점
- routeTree.gen.ts @tanstack/react-router에 의해 자동 업데이트됨
- styles.css
- index.html
- project.json
- vite.config.ts
- …
import * as url from 'url';import { Construct } from 'constructs';import { StaticWebsite } from '../../core/index.js';
export class GameUI extends StaticWebsite { constructor(scope: Construct, id: string) { super(scope, id, { websiteName: 'GameUI', websiteFilePath: url.fileURLToPath( new URL( '../../../../../../dist/packages/game-ui/bundle', import.meta.url, ), ), }); }}
이것은 GameUI를 정의하는 CDK 구문입니다. 보시다시피 Vite 기반 UI의 생성된 번들을 가리키도록 파일 경로가 이미 구성되어 있습니다. 이는 build
시점에 번들링이 game-ui 프로젝트의 빌드 대상 내에서 발생하며 그 출력이 여기서 사용됨을 의미합니다.
import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';
import '@cloudscape-design/global-styles/index.css';
const router = createRouter({ routeTree });
// 타입 안전성을 위해 라우터 인스턴스 등록declare module '@tanstack/react-router' { interface Register { router: typeof router; }}
const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RouterProvider router={router} /> </I18nProvider> </React.StrictMode>, );
이것은 React가 마운트되는 진입점입니다. 보시다시피 파일 기반 라우팅
구성으로 @tanstack/react-router
를 초기 구성합니다. 이는 개발 서버가 실행 중인 한 routes
폴더 내에 파일을 생성하기만 하면 @tanstack/react-router
가 보일러플레이트 파일 설정을 생성하고 routeTree.gen.ts
파일을 업데이트함을 의미합니다. 이 파일은 모든 라우트를 타입 안전한 방식으로 유지하므로 <Link>
를 사용할 때 to
옵션에 유효한 라우트만 표시됩니다. 자세한 내용은 @tanstack/react-router
문서를 참조하세요.
import { ContentLayout, Header, SpaceBetween, Container,} from '@cloudscape-design/components';import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/welcome/')({ component: RouteComponent,});
function RouteComponent() { return ( <ContentLayout header={<Header>Welcome</Header>}> <SpaceBetween size="l"> <Container>Welcome to your new Cloudscape website!</Container> </SpaceBetween> </ContentLayout> );}
/welcome
라우트로 이동할 때 렌더링될 컴포넌트입니다. @tanstack/react-router
는 이 파일을 생성/이동할 때 Route
를 관리합니다(개발 서버가 실행 중인 경우). 이는 이 튜토리얼의 뒷부분에서 확인할 수 있습니다.
게임 UI: 인증
섹션 제목: “게임 UI: 인증”이제 Amazon Cognito를 통해 인증된 접근을 요구하도록 Game UI를 구성해 보겠습니다. 다음 단계를 따르세요:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#react-website#auth
- 필수 매개변수 입력
- cognitoDomain: game-ui
- project: @dungeon-adventure/game-ui
- allowSignup: true
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#react-website#auth --cognitoDomain=game-ui --project=@dungeon-adventure/game-ui --allowSignup=true --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타나거나 변경된 것을 볼 수 있습니다.
ts#react-website#auth 업데이트된 파일
다음은 ts#react-website#auth
제너레이터에 의해 생성/업데이트된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리core/
- user-identity.ts 사용자/ID 풀 생성 CDK 구문
디렉터리types/
디렉터리src/
- runtime-config.ts cognitoProps 추가로 업데이트됨
디렉터리game-ui/
디렉터리src/
디렉터리components/
디렉터리AppLayout/
- index.tsx 로그인한 사용자/로그아웃을 헤더에 추가
디렉터리CognitoAuth/
- index.ts Cognito 로그인 관리
디렉터리RuntimeConfig/
- index.tsx
runtime-config.json
을 가져와 컨텍스트를 통해 자식에 제공
- index.tsx
디렉터리hooks/
- useRuntimeConfig.tsx
- main.tsx Cognito 추가로 업데이트됨
import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// 라우터 인스턴스 타입 안전성 등록declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <RouterProvider router={router} /> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
RuntimeConfigProvider
와 CognitoAuth
컴포넌트가 AST 변환을 통해 main.tsx
파일에 추가되었습니다. 이를 통해 CognitoAuth
컴포넌트는 runtime-config.json
을 가져와 필요한 Cognito 연결 구성을 가져와 백엔드 호출을 올바른 대상으로 보낼 수 있습니다.
게임 UI: Story API 연결
섹션 제목: “게임 UI: Story API 연결”이제 이전에 생성한 Story API에 연결하도록 Game UI를 구성해 보겠습니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - api-connection
- 필수 매개변수 입력
- sourceProject: @dungeon-adventure/game-ui
- targetProject: dungeon_adventure.story_api
- 클릭
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=dungeon_adventure.story_api --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타나거나 변경된 것을 볼 수 있습니다.
UI -> FastAPI api-connection 업데이트된 파일
다음은 api-connection
제너레이터에 의해 생성/업데이트된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리packages/
디렉터리game-ui/
디렉터리src/
디렉터리hooks/
- useSigV4.tsx StoryApi가 요청에 서명하는 데 사용
- useStoryApiClient.tsx StoryApi 클라이언트 구성 훅
- useStoryApi.tsx TanStack Query를 사용하여 StoryApi와 상호작용하는 훅
디렉터리components/
- QueryClientProvider.tsx TanStack Query 클라이언트 제공자
- StoryApiProvider.tsx StoryApi TanStack Query 훅 제공자
- main.tsx QueryClientProvider 및 StoryApiProvider 계측
- .gitignore 생성된 클라이언트 파일 무시
- project.json openapi 훅 생성 대상 추가로 업데이트됨
- …
디렉터리story_api/
디렉터리scripts/
- generate_open_api.py
- project.json openapi.json 파일 생성으로 업데이트됨
import { StoryApi } from '../generated/story-api/client.gen';import { useSigV4 } from './useSigV4';import { useRuntimeConfig } from './useRuntimeConfig';import { useMemo } from 'react';
export const useStoryApi = (): StoryApi => { const runtimeConfig = useRuntimeConfig(); const apiUrl = runtimeConfig.apis.StoryApi; const sigv4Client = useSigV4(); return useMemo( () => new StoryApi({ url: apiUrl, fetch: sigv4Client, }), [apiUrl, sigv4Client], );};
이 훅은 StoryApi
에 인증된 API 요청을 하는 데 사용할 수 있습니다. 구현에서 볼 수 있듯이 빌드 시 생성된 StoryApi
를 사용하므로 코드를 빌드할 때까지 IDE에서 오류가 표시됩니다. 클라이언트 생성 방법 또는 API 사용 방법에 대한 자세한 내용은 React to FastAPI 가이드를 참조하세요.
import { createContext, FC, PropsWithChildren, useMemo } from 'react';import { useStoryApiClient } from '../hooks/useStoryApiClient';import { StoryApiOptionsProxy } from '../generated/story-api/options-proxy.gen';
export const StoryApiContext = createContext<StoryApiOptionsProxy | undefined>( undefined,);
export const StoryApiProvider: FC<PropsWithChildren> = ({ children }) => { const client = useStoryApiClient(); const optionsProxy = useMemo( () => new StoryApiOptionsProxy({ client }), [client], );
return ( <StoryApiContext.Provider value={optionsProxy}> {children} </StoryApiContext.Provider> );};
export default StoryApiProvider;
위 제공자 컴포넌트는 useStoryApiClient
훅을 사용하고 StoryApiOptionsProxy
를 인스턴스화하여 TanStack Query 훅 옵션을 구성합니다. useStoryApi
훅을 사용하여 이 옵션 프록시에 접근할 수 있으며, tRPC API와 일관된 방식으로 FastAPI와 상호작용할 수 있습니다.
useStoryApiClient
가 스트리밍 API에 대한 비동기 이터레이터를 제공하므로 이 튜토리얼에서는 Vanilla 클라이언트를 직접 사용할 것입니다.
게임 UI: Game API 연결
섹션 제목: “게임 UI: Game API 연결”이제 이전에 생성한 Game API에 연결하도록 Game UI를 구성해 보겠습니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - api-connection
- 필수 매개변수 입력
- sourceProject: @dungeon-adventure/game-ui
- targetProject: @dungeon-adventure/game-api
- 클릭
Generate
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
yarn nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
npx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
bunx nx g @aws/nx-plugin:api-connection --sourceProject=@dungeon-adventure/game-ui --targetProject=@dungeon-adventure/game-api --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타나거나 변경된 것을 볼 수 있습니다.
UI -> tRPC api-connection 업데이트된 파일
다음은 api-connection
제너레이터에 의해 생성/업데이트된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리packages/
디렉터리game-ui/
디렉터리src/
디렉터리components/
- GameApiClientProvider.tsx GameAPI 클라이언트 설정
디렉터리hooks/
- useGameApi.tsx GameApi 호출 훅
- main.tsx trpc 클라이언트 제공자 주입
- package.json
import { GameApiTRCPContext } from '../components/GameApiClientProvider';
export const useGameApi = GameApiTRCPContext.useTRPC;
이 훅은 tRPC의 최신 React Query 통합을 사용하여 추가 추상화 계층 없이 @tanstack/react-query
와 직접 상호작용할 수 있게 합니다. tRPC API 호출 예제는 tRPC 훅 사용 가이드를 참조하세요.
import GameApiClientProvider from './components/GameApiClientProvider';import QueryClientProvider from './components/QueryClientProvider';import CognitoAuth from './components/CognitoAuth';import RuntimeConfigProvider from './components/RuntimeConfig';import React from 'react';import { createRoot } from 'react-dom/client';import { I18nProvider } from '@cloudscape-design/components/i18n';import messages from '@cloudscape-design/components/i18n/messages/all.en';import { RouterProvider, createRouter } from '@tanstack/react-router';import { routeTree } from './routeTree.gen';import '@cloudscape-design/global-styles/index.css';const router = createRouter({ routeTree });// 라우터 인스턴스 타입 안전성 등록declare module '@tanstack/react-router' { interface Register { router: typeof router; }}const root = document.getElementById('root');root && createRoot(root).render( <React.StrictMode> <I18nProvider locale="en" messages={[messages]}> <RuntimeConfigProvider> <CognitoAuth> <QueryClientProvider> <GameApiClientProvider> <RouterProvider router={router} /> </GameApiClientProvider> </QueryClientProvider> </CognitoAuth> </RuntimeConfigProvider> </I18nProvider> </React.StrictMode>, );
main.tsx
파일이 AST 변환을 통해 tRPC 제공자를 주입하도록 업데이트되었습니다.
게임 UI: 인프라스트럭처
섹션 제목: “게임 UI: 인프라스트럭처”이제 마지막으로 생성해야 할 하위 프로젝트는 CDK 인프라스트럭처입니다. 다음 단계에 따라 생성합니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#infra
- 필수 매개변수 입력
- name: infra
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
yarn nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
npx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
bunx nx g @aws/nx-plugin:ts#infra --name=infra --no-interactive --dry-run
파일 트리에 몇 가지 새 파일이 나타나거나 변경된 것을 볼 수 있습니다.
ts#infra 업데이트된 파일
다음은 ts#infra
제너레이터에 의해 생성/업데이트된 모든 파일 목록입니다. 파일 트리에서 강조 표시된 주요 파일 몇 가지를 살펴보겠습니다:
디렉터리packages/
디렉터리common/
디렉터리constructs/
디렉터리src/
디렉터리core/
디렉터리cfn-guard-rules/
- *.guard
- cfn-guard.ts
- index.ts
디렉터리infra
디렉터리src/
디렉터리stages/
- application-stage.ts CDK 스택이 정의됨
디렉터리stacks/
- application-stack.ts CDK 리소스가 정의됨
- index.ts
- main.ts 모든 스테이지를 정의하는 진입점
- cdk.json
- project.json
- …
- package.json
- tsconfig.json 참조 추가
- tsconfig.base.json 별칭 추가
import { ApplicationStage } from './stacks/application-stage.js';import { App, CfnGuardValidator, RuleSet,} from ':dungeon-adventure/common-constructs';
const app = new App({ policyValidationBeta1: [new CfnGuardValidator(RuleSet.AWS_PROTOTYPING)],});
// CLI 자격 증명을 사용하여 자신의 샌드박스 환경 배포new ApplicationStage(app, 'dungeon-adventure-infra-sandbox', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});
app.synth();
이것은 CDK 애플리케이션의 진입점입니다.
이것은 구성된 규칙 세트를 기반으로 cfn-guard
를 사용하여 인프라 유효성 검사를 실행하도록 구성됩니다. 이는 합성 후 계측됩니다.
import * as cdk from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props);
// 스택을 정의하는 코드가 여기에 위치합니다 }}
이곳에서 CDK 구문을 인스턴스화하여 던전 어드벤처 게임을 구축할 것입니다.
인프라스트럭처 업데이트
섹션 제목: “인프라스트럭처 업데이트”이미 생성된 일부 구문을 인스턴스화하기 위해 packages/infra/src/stacks/application-stack.ts
를 업데이트해 보겠습니다:
import { GameApi, GameUI, StoryApi, UserIdentity,} from ':dungeon-adventure/common-constructs';import { Stack, StackProps } from 'aws-cdk-lib';import { Construct } from 'constructs';
export class ApplicationStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props);
// The code that defines your stack goes here const userIdentity = new UserIdentity(this, 'UserIdentity');
const gameApi = new GameApi(this, 'GameApi', { integrations: GameApi.defaultIntegrations(this).build(), }); const storyApi = new StoryApi(this, 'StoryApi', { integrations: StoryApi.defaultIntegrations(this).build(), });
// grant our authenticated role access to invoke our APIs [storyApi, gameApi].forEach((api) => api.grantInvokeAccess(userIdentity.identityPool.authenticatedRole), );
// Ensure this is instantiated last so our runtime-config.json can be automatically configured new GameUI(this, 'GameUI'); }}
여기서 두 API에 기본 통합을 제공합니다. 기본적으로 API의 각 작업은 해당 작업을 처리하는 개별 람다 함수에 매핑됩니다.
코드 빌드
섹션 제목: “코드 빌드”Nx 명령어
단일 vs 다중 대상
섹션 제목: “단일 vs 다중 대상”run-many
명령은 여러 하위 프로젝트에 대해 대상을 실행합니다(--all
은 모두 대상). 종속성이 올바른 순서로 실행되도록 보장합니다.
단일 프로젝트 대상에 대해 빌드(또는 기타 작업)를 트리거할 수도 있습니다. 예를 들어 @dungeon-adventure/infra
프로젝트를 빌드하려면 다음 명령을 실행할 수 있습니다:
pnpm nx run @dungeon-adventure/infra:build
yarn nx run @dungeon-adventure/infra:build
npx nx run @dungeon-adventure/infra:build
bunx nx run @dungeon-adventure/infra:build
종속성 시각화
섹션 제목: “종속성 시각화”다음 명령으로 종속성을 시각화할 수 있습니다:
pnpm nx graph
yarn nx graph
npx nx graph
bunx nx graph

Nx는 개발 속도를 높이기 위해 이전 빌드의 아티팩트를 재사용하기 위해 캐싱에 의존합니다. 이를 올바르게 작동시키려면 일부 구성이 필요하며 캐시를 사용하지 않고 빌드를 수행하고 싶은 경우가 있을 수 있습니다. 이 경우 --skip-nx-cache
인수를 명령에 추가하면 됩니다. 예를 들어:
pnpm nx run @dungeon-adventure/infra:build --skip-nx-cache
yarn nx run @dungeon-adventure/infra:build --skip-nx-cache
npx nx run @dungeon-adventure/infra:build --skip-nx-cache
bunx nx run @dungeon-adventure/infra:build --skip-nx-cache
캐시를 지워야 하는 경우(저장된 .nx
폴더) 다음 명령을 실행할 수 있습니다:
pnpm nx reset
yarn nx reset
npx nx reset
bunx nx reset
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
다음과 같은 메시지가 표시됩니다:
NX The workspace is out of sync
[@nx/js:typescript-sync]: Some TypeScript configuration files are missing project references to the projects they depend on or contain outdated project references.
This will result in an error in CI.
? Would you like to sync the identified changes to get your workspace up to date? …Yes, sync the changes and run the tasksNo, run the tasks without syncing the changes
이 메시지는 NX가 자동으로 업데이트할 수 있는 일부 파일을 감지했음을 나타냅니다. 이 경우 참조 프로젝트에 대한 TypeScript 참조가 설정되지 않은 tsconfig.json
파일을 가리킵니다. Yes, sync the changes and run the tasks 옵션을 선택하여 진행하세요. 동기화 제너레이터가 누락된 TypeScript 참조를 자동으로 추가하므로 모든 IDE 관련 가져오기 오류가 자동으로 해결됩니다!
모든 빌드 아티팩트는 이제 모노레포 루트의 dist/
폴더 내에서 사용할 수 있습니다. 이는 @aws/nx-plugin
에서 생성된 프로젝트의 표준 관행으로, 생성된 파일이 파일 트리 전체에 흩어지지 않도록 합니다. 파일을 정리하려면 dist/
폴더를 삭제하면 됩니다.
축하합니다! 던전 어드벤처 게임의 핵심을 구현하기 위해 필요한 모든 하위 프로젝트를 생성했습니다. 🎉🎉🎉