AI 에이전트 Koal & KoalStudio

AaSL 2탄 — BE도 다시 그려요. AI 레이어가 깔린 다음의 도메인 설계

AI 서비스 레이어가 BE 위에 깔리면, 사람 한 명이 보던 API가 어디서부터 어색해지는지가 보여요. 응답·인증·모호성·에러·목록·변경 — 여섯 자리의 재설계 포인트를 Aster.duck 운영 경험을 바탕으로 정리합니다.

1탄에서는 AI를 “사용자와 BE 사이의 새 계층”이라고 정의했어요. AaSL(AI as Service Layer) 이라는 약어로 묶었어요. 이번 글은 그 다음 질문이에요.

AI 레이어가 깔리면 BE는 어디부터 어색해지나?

스킬셋을 굴려보면 한동안은 “기존 BE 그대로 두고, 스킬셋이 잘 부르도록 매니페스트만 잘 쓰면 되겠지”라고 생각하게 돼요. 한 달쯤 운영하면 그게 틀린 가정이라는 게 보여요.

사람 한 명만 보고 디자인된 API는 — 사람의 FE가 부족한 의미를 화면 위에서 보충해주고 있었어요. 화면 라벨, 컬러, 툴팁, 정렬 순서가 도메인 의미를 끌어안고 있었던 거예요. AI 레이어가 그 화면을 거치지 않고 BE를 직접 부르기 시작하면, 의미를 들고 오지 않는 응답의 부담이 그대로 노출돼요.

이 글은 그 부담을 어디서 어떻게 BE 쪽으로 옮겨와야 하는지, 우리가 마주한 여섯 자리의 재설계 포인트를 정리해요.


1) 응답은 의미를 들고 와야 해요

가장 흔한 패턴이에요. 사람 FE는 보통 가격을 "₩30,000"처럼 문자열로 받아 그대로 출력해요. 정렬·필터·환산은 거의 안 해요. 그래서 BE도 자연스럽게 표시용 문자열을 같이 보냈어요.

AI 레이어는 그 문자열을 다시 의미로 풀어야 해요. ”₩“이 통화 표식인지, 30,000이 원인지 일본 엔인지, 천 단위 구분이 쉼표인지 점인지 — 모델이 추측을 시작해요. 추측이 시작되면 사고가 시작돼요.

그래서 응답 모양을 의미 단위로 풉니다.

// before
{ "price_label": "₩30,000" }

// after
{ "price": { "amount": 30000, "currency": "KRW", "display": "₩30,000" } }

display는 사람 FE를 위해 유지해도 좋아요. 핵심은 amountcurrency가 항상 같이 와야 한다는 점이에요. 같은 패턴이 점수·평점·중량·날짜·기간 전부에 적용돼요.

원칙은 “FE가 보충하던 의미를 응답이 직접 들고 오게 한다” 예요. 표시는 옵션, 의미는 필수.


2) 인증은 단명 토큰 + 명시적 스코프

사람의 세션 토큰은 보통 길게 살아요. 30일 refresh, 7일 access 같은 모양이 흔해요. 사람은 한 디바이스에서 한 서비스를 쓰는 시간이 길고, 자주 끊기면 짜증나니까요.

AI 레이어는 다르게 살아요. 여러 사용자가 같은 모델을 통해, 짧고 강한 권한을 짧게 들고 들어와요. 길게 사는 토큰을 쥐어주면 한 번 새면 회수가 어렵고, 사용자가 “어떤 AI에게 무슨 권한을 줬는지”가 불명확해져요.

그래서 두 가지를 같이 해요.

  • 단명 Bearer 토큰 — 우리는 24시간을 기본값으로 잡았어요. 하루 한 번 로그인하면 그날은 안 끊긴다는 절충이에요.
  • 명시적 스코프scope: "skill" 같은 claim을 토큰에 박아요. 사람 세션 토큰과 AI 스킬 토큰을 분리하면, 발급·회수·감사 로그를 다르게 운영할 수 있어요.

매니페스트에는 어떤 스킬이 어떤 스코프를 필요로 하는지가 명시돼요. AI 레이어는 토큰을 받기 전에 스코프를 보고, 토큰 발급 흐름을 자기 책임으로 끌어가요.


3) 도메인 모델의 모호함을 줄여요

사람 FE에서는 모호함을 화면이 덮어줘요. 점수 입력 컴포넌트가 5점 만점인지 100점 만점인지를 컴포넌트 모양 자체가 알려주고, 와인 빈티지 입력란이 NV(빈티지 없음) 체크박스를 옆에 두고 있어요.

AI 레이어가 들어오면 그 덮개가 사라져요. 자연어 의도 "4점 줬어"가 들어왔을 때, BE의 도메인 모델이 모호하면 AI가 추측해요. 추측은 사고의 원인이에요.

운영하면서 우리가 손본 도메인 면들이에요.

  • 단위가 자명한 필드 이름
    • score 대신 score_5 또는 score: { value: 4, max: 5 }
    • aging 대신 aging_months
  • nullable에 의미 부여
    • 빈티지 = null이 “모름”인지 “NV”인지 분리. 별도 boolean is_nv 또는 vintage: { year: null, kind: "nv" }
  • 상태 다중값
    • status: "drinking" 단일 enum 대신 statuses: ["aging", "approachable"] 다중 — 같은 와인이 두 상태에 동시에 있을 수 있어요.
  • 시점 의미 분리
    • tasted_at (시음일)과 created_at (기록일)은 다른 시점이에요. 같은 필드로 덮으면 카운트가 망가져요.

원칙은 “도메인 단어가 한 가지 의미만 갖게 한다” 예요. 한 단어가 두 의미를 가지면, 화면이 사라진 순간 AI가 둘 사이를 떠돌아요.


4) 에러는 다음 행동을 코드로 들고 와야 해요

HTTP 401 Unauthorized 한 줄을 받았을 때, 사람 FE는 알아서 로그인 페이지로 보내요. FE 코드가 401에 대한 흐름을 알고 있으니까요.

AI 레이어는 그 흐름을 자기가 알지 못하면 멈춰요. “401이 떴어요”라고 사용자에게 말하고 끝나면, 사용자는 다음에 뭘 해야 하는지를 모르게 돼요.

그래서 에러 응답도 의미를 들고 와야 해요.

// before
{ "error": "Unauthorized" }

// after
{
  "error": {
    "code": "AUTH_TOKEN_EXPIRED",
    "message": "토큰이 만료됐어요",
    "next_action": {
      "kind": "reauth",
      "skill": "asterduck-auth",
      "hint": "재로그인 후 동일 의도를 다시 보내주세요"
    }
  }
}

code는 기계가 분기하기 위한 키, message는 사람을 위한 표현, next_action은 AI 레이어가 흐름을 끊지 않게 도와주는 안내예요. 셋이 같이 있어야 AI 레이어가 사고 없이 다음 단계로 넘어가요.

스킬셋 운영 사고의 절반 가까이가 “에러가 의미를 안 들고 와서 AI가 추측했다”였어요. 에러 응답 표준화의 ROI가 가장 높았어요.


5) List는 누락 없음을 BE가 보장해야 해요

사람 FE의 목록은 무한 스크롤이나 페이지네이션 컨트롤이 화면 위에 있어요. 사용자가 “더 보기”를 누르거나 스크롤하면 다음 페이지가 와요.

AI 레이어는 한 번에 도메인을 다 본 뒤 추론해야 할 때가 많아요. “이번 달 안에 마셔야 할 와인 뭐 있어?”는 셀러 전체를 한 번에 봐야 답할 수 있어요. 셀러가 26병인데 응답이 20병만 오면, “이번 달 안에 마실 와인”의 답이 6병을 누락한 채 만들어져요. 사고예요.

그래서 list 응답은 두 가지를 같이 줍니다.

  • per_page 상한이 명시되고 충분히 큰 default — 우리는 100을 기본값으로 잡았어요.
  • has_more 또는 total 둘 중 하나는 반드시 — AI 레이어가 “이게 전부인지” 확인할 수 있어야 해요.
{
  "items": [...],
  "page": 1,
  "per_page": 100,
  "total": 26,
  "has_more": false
}

매니페스트에는 “list 호출은 has_more=false까지 따라가라”가 패턴으로 박혀요. BE가 누락 없음을 보장해주지 않으면, AI 레이어가 보수적으로 한 번 더 호출하는 비용이 쌓여요.


6) 변경은 사람과 AI 둘 다에게 알려야 해요

API에 필드를 추가하거나 enum 값을 늘리면, 사람 FE는 PR을 통해 같이 변해요. 같은 회사 안에서 같은 사이클로 굴러가니까요.

AI 레이어는 다르게 움직여요. 매니페스트는 우리 저장소에 있지만, 그 매니페스트를 들고 도는 외부 AI는 우리 배포 사이클을 모르고, 일부 사용자는 며칠 묵은 매니페스트를 가지고 있을 수 있어요.

그래서 변경 운영을 두 갈래로 갖춰요.

  • 응답 필드는 추가만 한다(append-only) — 기존 필드를 절대 제거·이름 변경하지 않아요. enum 값도 새로 늘리되 기존 값은 의미를 바꾸지 않아요.
  • deprecate는 헤더로 신호한다Deprecation: true, Sunset: <date> 같은 응답 헤더를 추가하고, 같은 정보를 changelog 페이지에 publish해요. 매니페스트도 같은 사이클로 minor bump가 돼요.

변경이 보이지 않게 새는 것이 가장 위험해요. 한 번 새기 시작하면 어디서 어떤 사용자의 어떤 AI가 끊기는지 추적이 어렵거든요. 변경 자체보다 변경을 알리는 채널을 먼저 만들어두는 게 비용이 훨씬 적어요.


정리 — 사람을 위한 보충이 BE 안으로 들어와요

여섯 자리를 한 줄로 묶으면 이렇게 돼요.

사람 FE가 화면 위에서 보충하던 의미·흐름·안전장치를, BE 응답·인증·도메인 모델 안으로 옮겨와야 해요.

이건 BE가 무거워진다는 뜻이 아니에요. 원래 BE가 들고 있어야 할 의미를 BE가 들고 있게 되는 것 뿐이에요. 사람 FE 시절에는 화면이 모자란 의미를 채워줬어요. AI 레이어가 깔리면, 그 보충 채널이 사라져요. 그러니까 BE가 자기 의미를 자기 모양 안에 채워 넣어야 해요.

이 작업이 끝나면 부수 효과가 따라와요. 사람 FE 코드도 가벼워져요. “이 가격 라벨에 통화 기호를 어디에 붙일지”, “이 점수가 5점 만점인지” 같은 보충을 BE가 들고 오면, FE가 짊어지던 도메인 지식이 줄어들어요. FE 팀이 바뀌어도 도메인 의미가 흔들리지 않아요.

AI 레이어를 깐다는 결정은, 결과적으로 도메인의 자기 표현력을 한 단 올리는 일이에요. 그래서 회사의 기술 자산을 한 칸 더 두꺼워지게 만들어요. 모델이 commoditize되어도 흔들리지 않는 자산은 바로 이쪽이에요.

다음 글에서는 — 이 BE 재설계가 어떻게 사람 FE의 UX 결정에 거꾸로 영향을 주는지, 즉 AI 레이어가 깔린 뒤 화면이 어떻게 가벼워지고 어떤 화면은 도리어 진해지는지를 정리할 예정이에요.


링크

댓글

댓글 남기기

댓글 삭제 시 사용됩니다
공개되지 않습니다
0/2000
← 목록으로