
AI와 인간 모두에게 친숙한 Laravel 설계, Package By Feature
요약
AI 코딩 에이전트의 효율을 높이기 위해 기술 계층 중심의 Package By Layer 대신 기능 중심의 Package By Feature 설계를 제안합니다. 기능 단위로 디렉토리를 구성하면 AI의 컨텍스트 소비를 줄이고 설계 규칙을 강제하기 용이합니다.
핵심 포인트
- Package By Layer는 AI가 관련 파일을 찾기 위해 여러 디렉토리를 탐색해야 하는 비용이 발생함
- Package By Feature는 1기능 1디렉토리 구조로 AI의 컨텍스트 활용도를 극대화함
- 기능 단위 구성은 AI가 프로젝트의 암묵적인 설계 규칙을 더 쉽게 준수하도록 도움
- 새로운 기능 구현 시 기존 디렉토리를 템플릿으로 활용하여 AI 지시 효율을 높임
최근 1년 정도 사이 AI 코딩 에이전트에게 코드를 작성해 달라고 요청하는 기회가 부쩍 늘었다. 아니, 사실상 대부분의 코드를 작성해 달라고 하고 있다. 사용하면서 느낀 점은 Controller, Service, Repository와 같이 기술적인 계층(Layer)으로 디렉토리를 나누는 이른바 Package By Layer(기술 단위로 디렉토리를 나누는 구성)는 AI와 그리 궁합이 좋지 않다는 것이다.
반면, Package By Feature(기능 단위로 디렉토리를 나누는 구성)로 하면 AI와 인간 모두 다루기 쉬워지는 경우가 많았다. 이 기사에서는 그 이유와 실제로 어떤 구성을 했는지 샘플 리포지토리(Repository)를 곁들여 써 내려가 보겠다.
Laravel에서 흔히 볼 수 있는 구성은 다음과 같다.
app/
├── Http/Controllers/TaskController.php
├── Services/TaskService.php
...
Task라는 하나의 기능을 수정할 뿐인데, 파일은 4개의 디렉토리에 걸쳐 있다. 인간은 프로젝트에 대한 지형지물(Context)을 알고 있기에 그리 힘들지 않지만, AI에게 코드를 작성하게 할 때는 이 "여기저기 찾아다니는" 비용이 그대로 영향을 미친다.
구체적으로는,
- 관련 파일을 찾기 위해 여러 디렉토리를 grep 하거나 읽으러 가는 횟수가 늘어난다. 컨텍스트(Context)를 불필요하게 소비한다.
- Service가 어디까지 책임을 가질지, Repository는 어디까지 얇게(Thin) 유지할지 같은 암묵적인 규칙을 AI는 모른다. 매번 미묘하게 다른 설계로 작성된다.
- 영향 범위를 잘못 예측하여 관계없는 파일까지 건드려 버린다.
이러한 일들이 일어나기 쉬웠다.
기술적인 계층이 아니라, 기능(Feature)을 제1의 분할 단위로 삼는 사고방식. 1기능 = 1디렉토리로 하고, 해당 기능에 관한 코드는 원칙적으로 그 디렉토리 안에 가두는 것이다.
app/Features/Task/
├── TaskController.php
├── Requests/
...
Controller, UseCase(비즈니스 로직), Resource와 같은 역할 자체는 이전과 동일하게 나누어져 있지만, 그것들은 "Task"라는 디렉토리 안에 갇혀 있다. 무언가를 수정할 때는 이 1개의 디렉토리만 보면 된다.
이유는 단순하다. 1기능의 변경에 필요한 정보가 1개의 디렉토리에 수렴되어 있기 때문이다. AI에게 "Task에 ◯◯라는 항목을 추가해줘"라고 부탁하면, app/Features/Task/ 아래만 보고도 완결할 수 있다. 다른 Feature의 Service나 Repository를 신경 쓸 필요가 없다.
새로운 Feature를 만들 때도 기존의 Feature 디렉토리를 통째로 복사해서 이름을 바꾸는 것만으로 템플릿이 된다. 이는 AI에게 지시할 때도 편리해서, "Task 디렉토리와 같은 구성으로 Order라는 기능을 만들어줘"라고 부탁하기만 해도 기존 규칙에 따른 구현을 받아내기 쉽다.
계층화된(Layered) 구성에서는 "Service의 책임", "Repository의 입도(Granularity)"와 같이 코드만으로는 읽어내기 어려운 암묵적인 규칙이 AI와의 사이에서 어긋나기 쉽다. Package By Feature는 그러한 규칙을 디렉토리 구조 자체로 강제할 수 있으므로, AI에 대한 지시 비용이 낮아진다.
Feature 내부는 Controller/UseCase/Resource와 같이 기술 계층으로 나누고 있지만, 이는 AI가 찾아가는 범위가 "1개 디렉토리 내"에 수렴한다면 문제가 되지 않는다. Feature "간"에 기술 계층이 걸치지 않게 하는 것이 중요하다고 생각한다.
지금까지 AI와의 궁합에 대해 이야기해 왔지만, 코딩을 AI에게 전부 맡길 수 있다면 인간이 읽기 편한지는 상관없다는 식으로 결론 내릴 수는 없다고 생각한다.
AI는 무제한이 아니다. 개인 계약 플랜에도 기업의 이용에도 예산 상한이 있으며, 아주 예산이 풍족한 조직이 아닌 이상 월 중간에 한도에 도달하거나, 비용 의식 때문에 AI에 의존하지 않고 직접 작성하는 상황은 흔히 발생한다. 즉, 인간이 코드를 직접 읽거나 작성할 가능성은 AI 시대에도 (※현 시점에서는) 충분히 남아 있다.
따라서 "AI를 위해 최적화되어 있지만 인간에게는 읽기 어려운" 구성은 곤란하다. 다행히 Package By Feature는 기술 계층을 횡단할 필요가 없는 만큼 인간에게도 인지 부하가 낮다. "Task의 동작을 고치고 싶다면 app/Features/Task/를 보면 된다"는 것은 AI에게도, 리뷰하는 인간에게도 동일하게 성립한다. AI와 인간을 위해 별도의 최적화를 할 필요 없이 양쪽 모두에게 효과적이라는 것이, 이 구성을 추천하는 가장 큰 이유이다.
실제로 직접 손을 움직여 테스트한 결과물을 laravel-feature-template라는 이름으로 리포지토리에 올려두었다. 특정 프로젝트의 비즈니스 로직은 포함되어 있지 않으며, Task라는 범용적인 CRUD만을 샘플로 동봉하고 있다.
src/app/
├── Features/
│ └── Task/ # 1기능 = 1디렉토리
...
요청(Request)은 Router → Middleware → Controller → FormRequest → Input DTO → UseCase → Model → Resource라는 일직선 경로로 흐른다. Task의 생성(store) 처리를 예로 들어 살펴보자.
Controller는 얇게 유지되며, 1개의 메서드가 1개의 액션(Action)을 담당한다.
public function store(StoreTaskRequest $request, StoreTaskAction $action): JsonResponse
{
return (new TaskResource($action->execute($request->toInput())))
...
FormRequest는 유효성 검사(Validation)를 수행한 후, Input DTO로 변환하는 역할까지 담당한다.
public function rules(): array
{
return [
...
UseCase는 비즈니스 로직 본체이다. execute() 메서드를 단 하나만 가진다.
final class StoreTaskAction
{
public function execute(StoreTaskInput $input): Task
...
Package By Feature는 은탄환(Silver Bullet)이 아니다. 기능(Feature) 간에 걸쳐 있는 처리가 늘어나는 프로젝트에서는 Shared를 어디에 둘지 고민하는 상황이 생길 것이고, 작은 애플리케이션에서는 솔직히 Feature로 나눌 필요조차 없는 경우도 있다.
그럼에도 불구하고, AI와 함께 코드를 작성하는 기회가 늘어난 지금, "하나의 변경이 하나의 디렉토리에서 완결된다"는 성질은 AI의 컨텍스트(Context) 소비를 줄이는 측면에서도, 인간이 읽는 측면에서도 효과적이라는 실감이 든다. 아직 시도하는 중이지만, 비슷한 고민을 하는 분들에게 참고가 되기를 바란다.
참고로 중~대규모 프로젝트에서는 Features/StoreTask/와 같이 액션(Action) 단위로 디렉토리를 나누는 방법(Vertical Slice Architecture?)도 좋을 수 있다고 생각하고 있다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기