사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
스크립트
스크립트는 앱 서버를 띄워 작업 하나를 하고 끝나는 TypeScript 파일입니다. 프롬프트에서 치고 끝낼 일이 아니라 파일로 남길 일에 씁니다:
| 명령 | 형태 |
|---|---|
| ↳ 잘 맞는 일 | |
akan script | 검토하고 다시 돌릴 수 있는 script/ 속 파일 |
| 시드 데이터, 마이그레이션, 점검, 작은 유지보수 수정 | |
akan console | 닫으면 사라지는 프롬프트 |
| 서비스 점검, 쿼리 시험, 작은 운영 명령 하나 | |
- 앱과 같은 구성. 앱이 이미 연결해 둔 서비스, 시그널, 어댑터를 그대로 씁니다.
- 요청도 스케줄도 없음. 포트를 열지 않고, 앱의 init·interval·cron·queue 작업도 실행하지 않습니다.
- 작게, 쓰고 버리게. 스크립트 하나에는 작업 하나만 담고, 작업이 끝나면 지웁니다.
만들고 실행하기
파일을 앱의
script/ 폴더 바로 아래에 두고, 그 이름을 akan script에 넘깁니다.apps/koyo/script/hello.ts를 만듭니다. 안에 들어갈 내용은 다음 섹션에 있습니다.- 워크스페이스 루트에서 앱 이름과 파일 이름을 넘깁니다. 아래 명령은
apps/koyo/script/hello.ts를 실행합니다:
Terminal
인자
두 인자 모두 빼도 되며, 빼면 명령이 물어봅니다. 순서대로 받으므로 앱 이름이 먼저 옵니다:
appString선택
앱 이름이며, 파일 이름을 넘길 때는 꼭 적습니다. 빼면 목록에서 고르고, 앱이 하나뿐이면 그 앱을 씁니다.
filenameString선택
script/ 바로 아래 파일입니다. .ts는 붙여도 빼도 됩니다. 빼면 .ts 파일 목록에서 고릅니다.- 하위 폴더는 안 됩니다.
/나..이 들어간 이름은 거부되므로, 스크립트는 모두script/바로 아래에 둡니다. - 앱 폴더에서 실행됩니다. 작업 디렉터리가
apps/koyo/이므로 상대 경로는 거기서 시작합니다.
서버 시작과 종료
모든 스크립트의 뼈대는 같습니다. 서버를 시작하고, 작업하고,
finally에서 서버를 멈춥니다. 가장 작은 스크립트는 이렇습니다:apps/koyo/script/hello.ts
server는 앱의 것입니다.apps/koyo/server.ts에서 가져오므로, 스크립트는 앱과 똑같은 모듈로 부팅합니다.start()는 앱을 조립하되 포트는 열지 않습니다.akan script에서는 데이터베이스를 연결하고 어댑터, 서비스, 시그널을 만드는 데서 멈춥니다.stop()은finally에 둡니다. 작업이 예외를 던져도 데이터베이스 연결, 타이머, 어댑터가 제대로 정리됩니다.
서비스로 작업하기
작업은 데이터베이스에 직접 쓰지 말고 서비스를 거쳐 합니다. 서비스는 도메인 규칙, 데이터베이스 접근, 다른 의존성을 이미 알고 있습니다.
다음 스크립트는
served 상태로 남은 아이스크림 주문을 모두 완료 처리합니다:apps/koyo/script/finishServedOrders.ts
server.get(srv.IcecreamOrderService)는 클래스로 서비스를 찾으므로 모든 메서드에 타입이 붙습니다.listByStatuses는 모델의byStatuses필터에서 나옵니다. 필터마다 서비스에 이런list<Filter>가 생깁니다.finishIcecreamOrder는 앱과 같은 상태 검사를 거치므로,served가 아닌 주문은 거부됩니다.
인스턴스 찾기
server.start()가 끝나면 server가 앱에 등록된 서비스, 시그널, 어댑터를 무엇이든 꺼내 줍니다. 이름 문자열은 타입 검사가 안 되므로 가능하면 클래스로 찾습니다.| 찾는 기준 | 호출 |
|---|---|
| ↳ 꺼내는 것 | |
| 클래스 | server.get(srv.IcecreamOrderService) |
| 서비스, 시그널, 어댑터 인스턴스를 타입과 함께 꺼냅니다. | |
| 역할 | server.get(StorageAdaptorRole) |
| 구현과 상관없이 앱이 실제로 쓰는 스토리지 어댑터를 꺼냅니다. | |
| refName | server.getService("icecreamOrder") |
| 서비스를 꺼냅니다. | |
| refName | server.getSignal("icecreamOrder") |
| 시그널 로직을 실행해야 할 때 시그널을 꺼냅니다. | |
| refName | server.getAdaptor("blobStorage") |
| 인프라 작업에 쓸 어댑터를 꺼냅니다. | |
- refName은 모듈이 등록된 이름입니다. 서비스와 시그널은
icecreamOrder같은 camelCase 모듈 이름이고, 어댑터는adapt()에 넘긴 키입니다. - lib의 클래스는 lib 이름 아래에 있습니다.
srv.shared.UserService처럼 씁니다. StorageAdaptorRole은akanjs/service에서 가져옵니다. 앱이 구현을 바꿔 끼워도 역할로는 그 어댑터를 찾을 수 있습니다.


조회는 start 뒤에만 합니다.
await server.start()가 끝나기 전에는 모든 조회가 예외를 던집니다.데이터를 안전하게 바꾸기
데이터를 바꾸는 스크립트는 실제로 바꾸기 전에 무엇을 할지 먼저 보여 줘야 합니다. 세 가지 습관이면 대부분 해결됩니다:
- 대상 환경부터 출력합니다.
getEnv().environment가 스크립트가 바꿀 환경을 알려 줍니다. - 기본은 dry run으로 둡니다. 바꿀 대상을 보여 주기만 하는 실행입니다.
APPLY=1같은 환경 변수를 읽고, 이 값이 없으면 아무것도 바꾸지 않습니다. - 쓰기는 서비스 메서드로 합니다. 그래야 도메인 규칙이 스크립트에 복사되지 않고 한곳에 남습니다.
위의 스크립트에 앞의 두 습관을 더하면 이렇습니다:
apps/koyo/script/finishServedOrders.ts
한 번 실행해 개수를 확인하고, 다시 실행해 적용합니다:
Terminal
- 환경은
AKAN_PUBLIC_ENV가 정합니다. 서버는 그 값에 맞는env/env.server.<env>.ts를 읽습니다. 새 워크스페이스의.env에는local로 들어 있습니다. try안의return도finally를 거칩니다. 그래서 dry run에서도 서버가 멈춥니다.


파일 이름 뒤에 적은 것은 스크립트로 가지 않습니다.
akan script는 앱 이름과 파일 이름만 받고, 인자를 더 붙이거나 모르는 플래그를 붙이면 오류가 납니다. 환경 변수는 스크립트까지 그대로 가므로, 플래그는 환경 변수로 넘깁니다.