> ## Content Index
> Fetch the complete content index at: https://blog.sorune.org/llms.txt
> Use this file to discover other available public pages before exploring further.

# AI가 매번 같은 코드를 다시 쓰게 하지 않기로 했다 — 개인 소프트웨어 자산화의 기준
- URL: https://blog.sorune.org/reusable-software-assets-ai-boilerplate/
- Published: 2026-09-22T01:52:32.000Z
- Updated: 2026-09-22T01:52:32.000Z
- Description: 프로젝트가 늘어나자 AI에게 같은 controller, DTO, cache adapter, UI 구조를 반복 생성시키는 비용이 보이기 시작했다. 모든 것을 generic framework로 만들지 않으면서 재사용 자산을 쌓는 기준을 정리한다.
- Author: Sorune — Engineering, Photography & Lab Notes
- Tags: Engineering, Reusable Assets, Code Generation, AI-Native Engineering, Architecture, Developer Tools

AI를 쓰면 boilerplate를 만드는 비용이 거의 0처럼 느껴질 때가 있다.

controller 하나, DTO 몇 개, repository와 service, Redis adapter, 공통 UI component 정도는 다시 만들어도 금방 나오기 때문이다.

그래서 처음에는 크게 신경 쓰지 않았다.

그런데 프로젝트 수가 늘어나고 같은 패턴을 몇 번 반복하자 이상한 비용이 보이기 시작했다.

```text
코드는 빨리 생성됨
하지만 매번 다시 설명해야 함

비슷한 코드가 여러 repository에 생김
하지만 서로 조금씩 달라짐

검증했던 구조를 다시 생성함
하지만 다시 review해야 함

```

결국 "AI가 빨리 써준다"는 것과 "다시 만들 가치가 있다"는 것은 다른 문제였다.

그래서 개인 프로젝트에서 반복해서 쓰는 코드를 하나의 **소프트웨어 자산**으로 보기 시작했다.

## 재사용의 출발점은 generic framework가 아니었다

처음부터 거대한 공통 framework를 만들려는 것은 아니다.

오히려 과거에 비슷한 시도를 하면서 한계를 이미 경험했다.

PhotoGram에는 한때 generic CRUD service 계열의 실험이 있었다.

공통 JPA persistence, DTO/entity mapping, save/update 같은 반복을 줄이는 데는 잘 맞았다.

하지만 프로젝트가 커지면서 다음 의미까지 generic layer가 삼키기 시작하면 문제가 생겼다.

```text
ownership
authorization
attachment cleanup
transaction policy
event semantics
domain invariant

```

이런 것은 "모든 entity에 공통"인 척하기 어렵다.

결국 얻은 학습은 단순했다.

```text
반복되는 mechanics
!= 반복되는 domain meaning

```

mechanics는 재사용하거나 생성할 수 있다.

domain meaning은 concrete service가 소유해야 한다.

## 가장 좋은 자산화 사례는 실제 consumer가 있는 코드였다

현재 내 코드 중 가장 명확한 재사용 성공 사례는 grid-masonry다.

처음부터 library로 설계한 것이 아니라 제품 안에서 실제 문제를 해결하다가 deterministic masonry geometry가 제품 밖에서도 독립적인 contract를 가질 수 있다는 것을 발견했다.

흐름은 다음과 같았다.

```text
product implementation
→ reusable boundary 발견
→ 별도 repository 추출
→ contract / test 정리
→ 실제 consumer 검증
→ package / release

```

이 순서가 중요했다.

먼저 "언젠가 쓸 것 같은" abstraction을 만든 것이 아니라 실제 반복과 consumer가 있었다.

그래서 자산화 기준도 이쪽에 맞췄다.

```text
REUSE SHOULD FOLLOW EVIDENCE
NOT ANTICIPATION

```

## NestTemplate에서 원했던 것도 사실 runtime abstraction이 아니었다

예전에 NestTemplate이라는 실험을 한 적이 있다.

겉으로 보면 NestJS 프로젝트 template처럼 보이지만 내가 원했던 핵심은 조금 달랐다.

예를 들어:

```bash
nest set architecture mvc
nest g module user

```

처럼 architecture preset을 고르면 module 하나를 만드는 명령으로 서로 연결된 파일들이 같이 나오는 형태였다.

```text
user/
├─ user.module.ts
├─ user.controller.ts
├─ user.service.ts
├─ user.entity.ts
└─ dto/
   ├─ create-user.dto.ts
   ├─ update-user.dto.ts
   └─ user-response.dto.ts

```

그리고 field도 여러 파일에 반복해서 입력하고 싶지 않았다.

```text
User
- id
- email
- nickname
- createdAt

```

를 한 번 정의하면:

```text
canonical field definition
        ↓
Entity
Create DTO
Update DTO
Response DTO
validation metadata
accessor

```

로 필요한 surface에 투영하는 형태다.

지금 다시 보면 중요한 것은 "Nest 전용 만능 framework"가 아니었다.

```text
architecture preset
+ relationship rule
+ deterministic generation

```

이 더 정확한 문제였다.

## Generate와 Patch는 위험도가 다르다

code generator를 생각하면 기존 source까지 자동으로 고치고 싶어진다.

예를 들어 module을 생성한 뒤 기존 app module의 import를 알아서 수정하거나, decorator와 config를 merge하는 식이다.

하지만 이 둘은 같은 자동화가 아니다.

```text
GENERATE
= 새 path에 deterministic file 생성

PATCH / SYNC
= 기존 source 의미를 읽고 변경

```

Generate는 비교적 닫힌 문제다.

입력 schema와 template, target path가 있으면 결과를 예측하기 쉽다.

반면 Patch는 기존 code structure와 local convention을 이해해야 한다.

`import merge`, `decorator update`, `config mutation`, `existing code rewrite` 같은 순간부터 의미 해석이 들어간다.

그래서 초기 자산 materialization은 가능한 한 Generate 쪽에 두고, Patch/Sync는 실제 반복 필요성이 확인됐을 때 별도 문제로 다루는 편이 낫다고 봤다.

## Capability와 기술 이름도 분리해야 했다

재사용 자산을 정리하다 보면 흔히 기술 이름을 capability처럼 부른다.

```text
Redis module
Gmail module
PostgreSQL module

```

하지만 장기적으로는 의미를 분리하는 편이 낫다.

예를 들어 mail은:

```text
Mail capability
      ↓
MailPort
  ├─ SMTP adapter
  ├─ Gmail adapter
  └─ SES adapter

```

cache도:

```text
Cache capability
  ├─ Redis
  └─ in-memory

```

처럼 볼 수 있다.

```text
MAIL != GMAIL
CACHE != REDIS
PERSISTENCE != POSTGRESQL

```

특정 기술은 교체 가능한 adapter가 될 수 있고, 제품이 원하는 capability 의미는 그 위에 남는다.

이렇게 해두면 reusable asset이 특정 프로젝트의 infrastructure choice를 강제하지 않는다.

## UI도 framework 하나에 묶이지 않을 수 있다

sorune-ui를 생각하면서도 비슷한 결론에 도달했다.

React component library 하나만 만들겠다는 것보다 내가 반복해서 사용하는 시각 언어와 interaction pattern을 여러 presentation target에 재사용하는 쪽이 더 맞았다.

```text
React
HTML
JSP
Thymeleaf
Vue

```

가 모두 같은 runtime component를 공유할 필요는 없다.

대신 token, layout rule, interaction contract, component semantics, reference implementation 같은 자산을 공유할 수 있다.

framework-specific implementation과 design authority를 같은 것으로 만들지 않는 것이다.

## AI 시대에는 자산화가 context compression 역할도 한다

이 방향을 정리하면서 가장 크게 달라진 부분은 AI 비용을 보는 방식이었다.

AI가 다음 코드를 매번 다시 쓰는 것은 어렵지 않다.

```text
module skeleton
controller skeleton
service skeleton
DTO duplicate fields
entity duplicate fields
getter/setter
dependency injection wiring
common config
adapter setup

```

하지만 새 판단도 거의 없다.

그럼에도 매번 prompt에 구조를 설명하고, 결과를 읽고, 작은 차이를 수정하고, 다시 테스트한다.

AI가 실제로 시간을 써야 하는 부분은 오히려 다음이다.

```text
domain invariant
business rule
ownership
authorization
transaction semantics
special query
exceptional integration
migration / policy decision

```

그래서 장기적으로 원하는 흐름은 다음과 같다.

```text
BEFORE

AI
→ boilerplate
→ wiring
→ common config
→ known adapter
→ domain logic

AFTER

accepted reusable asset
→ boilerplate / wiring / known adapter materialization

AI
→ domain delta
→ exceptional behavior
→ business semantics

```

이건 단순한 코드 재사용보다 **context와 output을 압축하는 방법**에 가깝다.

```text
"다시 500줄 만들어줘"
보다
"검증된 asset X를 사용하고 이번 domain delta만 구현해"

```

가 더 싸고 검토하기도 쉽다.

## 모든 것을 지금 당장 자산으로 만들지는 않는다

재사용 이야기를 시작하면 금방 범위가 커진다.

```text
asset inventory
→ registry
→ universal CLI
→ framework adapter
→ automatic project composition
→ asset platform

```

하지만 이걸 한 번에 만들면 "중복 구현을 줄이기 위한 시스템" 자체가 새로운 대형 제품이 된다.

그래서 현재 원칙은 반대다.

```text
1. 이미 존재하는 자산을 찾는다
2. reusable / candidate / product-specific / legacy를 구분한다
3. 실제 반복되는 capability를 확인한다
4. 반복이 증명된 것부터 추출한다
5. generator는 기계적인 관계에만 붙인다

```

두 번째, 세 번째 실제 consumer가 생기기 전에는 common engine을 서둘러 만들지 않는다.

## 바퀴를 다시 만들지 않는 것과 abstraction을 많이 만드는 것은 다르다

이 작업의 목적을 한 문장으로 줄이면 "바퀴를 다시 만들지 않는다"가 맞다.

하지만 그 해결책이 모든 코드를 generic하게 만드는 것은 아니다.

```text
DO NOT REIMPLEMENT KNOWN MECHANICS
+
DO NOT ERASE DOMAIN MEANING

```

이 두 조건을 같이 만족해야 한다.

반복되는 파일 관계와 boilerplate는 기계가 처리한다.

이미 검증된 capability는 reusable asset으로 붙인다.

그 위에서 각 제품은 자기 domain invariant를 계속 소유한다.

AI를 많이 쓰기 시작하면서 오히려 이 구분이 더 중요해졌다.

코드를 생성하는 비용이 싸졌기 때문에 재사용의 필요성이 사라진 것이 아니라, **비슷하지만 서로 다른 코드가 무한히 증식할 가능성**이 커졌기 때문이다.

앞으로 자산화의 기준은 "만들 수 있는가"가 아니라 "이미 반복되었고, 독립된 contract가 있으며, 다시 검증할 가치가 없는 mechanics인가"에 두려고 한다.