이커머스에서의 Vibe Coding: AI 생성 모듈의 80%가 프로덕션에 배포되지 못하는 이유
요약
AI가 생성한 코드가 프로토타입이나 데모 환경에서는 작동하지만, 실제 운영 환경(Production)의 복잡성을 감당하지 못하는 문제를 지적합니다. 특히 이커머스 플랫폼(PrestaShop)처럼 버전별, 컨텍스트별로 변수가 많은 곳에서 AI 코드만으로는 도메인 전문 지식 없이는 안정적인 모듈 개발이 어렵다는 점을 강조합니다.
핵심 포인트
- AI 코드는 데모에는 좋으나 프로덕션 환경의 복잡성을 처리하기 어려움
- PrestaShop 등 이커머스 플랫폼은 버전, 컨텍스트에 따른 변수가 매우 많음
- 단순한 코드 생성보다 도메인 전문 지식(Domain expertise)이 필수적임
이커머스에서의 Vibe Coding: AI 생성 모듈의 80%가 프로덕션에 배포되지 못하는 이유
읽기 시간: 15분****최종 업데이트: 2026년 2월
그들이 판매하는 꿈 vs. 현장의 현실
“원하는 것을 설명하면, AI가 코드를 작성해 줍니다.”
Gene Kim이 Vibe Coding 개념을 대중화하고 Cursor, Claude Code, GitHub Copilot과 같은 도구들이 급격히 등장한 이후, 매혹적인 서사가 자리 잡았습니다. 이제 누구나 소프트웨어를 만들 수 있다는 것입니다. 코드를 이해할 필요 없이, 그저 “분위기(vibe)를 전달하기만 하면” 됩니다.
솔직히 말해서? 프로토타입(Prototype), 데모(Demo), 사이드 프로젝트(Side project)를 위해서는... 효과가 있습니다. 심지어 인상적이기까지 합니다.
하지만 저는 10년 이상 PrestaShop 모듈을 개발해 왔습니다. 월간 50,000건의 주문을 처리하는 상점에서 실행되는 모듈들을 다뤄왔습니다. 12개의 상점, 4개의 언어, 3개의 통화, 각 고객 그룹별로 특화된 비즈니스 규칙, 그리고 그 모든 뒤에 연결된 ERP가 있는 멀티숍(Multi-shop) 아키텍처에 설치된 모듈들도 다뤄왔습니다.
그리고 지난 6개월 동안 제가 수행한 감사(Audit), 코드 인수(Code takeover), 그리고 기술 지원 요청을 통해 목격한 것은, 모두 동일한 운명을 공유하는 “Vibe-coded” 모듈의 물결이었습니다:
데모에서는 작동하지만, 프로덕션(Production)에서는 깨집니다.
이 글은 반(反) AI 선언문이 아닙니다. 저 또한 매일 워크플로(Workflow)에서 AI를 사용합니다. 하지만 저는 구체적인 사례와 실제 코드를 통해, 이커머스 — 특히 PrestaShop — 에 적용된 Vibe Coding이 왜 오직 도메인 전문 지식(Domain expertise)으로만 헤쳐 나갈 수 있는 지뢰밭인지 보여드리고자 합니다.
1. Hooks: AI가 이해하지 못하는 제1의 함정
AI가 생성하는 것
LLM에게 제품 페이지에 안심 문구 블록(Reassurance block)을 표시하는 모듈을 만들어 달라고 요청해 보십시오. 다음과 같은 결과물을 얻게 될 것입니다:
<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayProductAdditionalInfo</span><span class="p">(
<span class="p">$params</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$product</span> <span class="o">=</span> <span class="nv">$params</span><span class="p">[</span><span class="s1">'product'</span><span class="p">];</span>
...
깔끔해 보입니다. 새로운 PrestaShop 설치 환경에서도 잘 작동합니다. 클라이언트는 48시간 동안 만족해합니다.
프로덕션(Production) 환경에서 터지는 문제들
문제 1: $params['product']는 당신이 생각하는 그것이 아닙니다.
PrestaShop 버전(1.7.6 vs 1.7.8 vs 8.1)에 따라, 표준 상품 페이지에 있는지 혹은 퀵 뷰(quick-view) 모듈에 있는지에 따라, 그리고 사용 중인 테마에 따라... $params['product']는 다음과 같을 수 있습니다:
Product객체 (Object)- 연관 배열 (Associative array,
ProductPresenter로부터 생성됨) null(예상치 못한 컨텍스트에서 훅(Hook)이 호출되는 경우)- 버전에 따라 서로 다른 키(Key)를 가진 배열
견고한(Robust) 코드는 다음과 같습니다:
<span class="k">public</span> <span class="k">function</span> <span class="n">hookDisplayProductAdditionalInfo</span><span class="p">(</span><span class="nv">$params</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// product 파라미터에 대한 방어적 처리 (Defensive handling)</span>
...
멋진가요? 아니요. 필수적인가요? 절대적으로 그렇습니다.
문제 2: 훅(Hook) 자체가 존재하지 않을 수도 있습니다.
AI는 공식 문서에 있기 때문에 hookDisplayProductAdditionalInfo를 제안할 것입니다. 하지만 커스텀 테마(Flavor, Warehouse 등)를 사용하는 실제 상점에서는 이 훅이:
- 템플릿에서 제거되었을 수 있습니다.
- DOM 내의 다른 위치에서 호출될 수 있습니다.
- 해당 훅에 등록된 다른 4개의 모듈과 충돌할 수 있습니다.
시니어 개발자는 대상 테마를 확인하고, 대안으로 위젯(Widget)을 제안하며, 필요한 경우 hookDisplayFooterProduct 또는 hookDisplayOverrideTemplate을 사용하여 폴백(Fallback)을 구현해야 한다는 점을 알고 있습니다.
문제 3: 잊혀진 액션 훅(Action hooks).
AI는 디스플레이 훅(Display hooks)은 매우 잘 생성합니다. 하지만 중요한 액션 훅(Action hooks)은 체계적으로 잊어버립니다. 제가 지난달에 감사(Audit)했던 바이브 코딩(Vibe-coded)된 재고 관리 모듈은 다음과 같았습니다:
hookActionProductUpdate를 처리하여 재고를 재계산함hookActionObjectProductDeleteAfter를 누락함 → 데이터베이스에 유령 상품(ghost products)이 남음hookActionProductAttributeUpdate를 누락함 → 상품 조합(product combinations)이 전혀 동기화되지 않음hookActionObjectCombinationDeleteAfter를 누락함 → ERP 시스템 충돌 발생hookActionObjectStockAvailableUpdateAfter를 처리하지 않음 → 기본(native) 재고와 충돌 발생
하나의 누락된 훅(hook) = 수백 개의 상품에 걸친 데이터 불일치.
2. 보안: AI가 남기는 거대한 구멍
보호되지 않은 AJAX
이는 관리자 인터페이스가 있는 **바이브 코딩(vibe-coded)된 모듈의 90%**에서 발견되는 패턴입니다:
<span class="c1">// front/ajax.php — AI 생성</span>
<span class="k">include</span><span class="p">(<span class="s1">'../../config/config.inc.php'</span><span class="p">);</span>
...
이 코드는 활짝 열린 문과 같습니다. 누구나 간단한 curl 호출만으로 상점 내 모든 상품의 가격을 변경할 수 있습니다:
curl <span class="s2">"https://your-store.com/modules/mymodule/front/ajax.php?action=updatePrice&id_product=1&price=0.01"</span>
축하합니다: 이제 당신의 모든 상품 가격은 1센트가 되었습니다.
보안 코드가 요구하는 사항
<span class="c1">// controllers/front/ajax.php — 보안 버전</span>
<span class="kd">class</span> <span class="nc">MyModuleAjaxModuleFrontController</span> <span class="kd">extends</span> <span class="nc">ModuleFrontController</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">initContent</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// 실제로 AJAX 요청인지 확인합니다</span>
<span class="k">if</span> <span class="p">(!</span><span class="nv">$this</span><span class="o">-></span><span class="n">ajax</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'잘못된 요청입니다'</span><span class="p">]));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">parent</span><span class="o">::</span><span class="nf">initContent</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">displayAjaxUpdatePrice</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// 1. CSRF 토큰 검증 (CSRF token verification)</span>
<span class="k">if</span> <span class="p">(!</span><span class="nv">$this->isTokenValid()</span> <span class="p">{</span>
<span class="nb">header</span>(<span class="s1">'HTTP/1.1 403 Forbidden'</span>);<span class="p"></span>
<span class="nv">$this->ajaxRender</span>(<span class="nb">json_encode</span>([<span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Invalid token'</span>]));<span class="p"></span>
<span class="k">return</span>;<span class="p"></span>
<span class="p">}</span>
<span class="c1">// 2.</span>
<span class="c1">// 2.</span>
Permission check (admin employee logged in)<span class="nv"></span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Cookie</span><span class="p"></span>(<span class="s1">'psAdmin'</span><span class="p"></span>);<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv"></span><span class="o">-></span><span class="n">id_employee</span><span class="p"></span>)<span class="p">{</span>
<span class="nb">header</span><span class="p"></span>(<span class="s1">'HTTP/1.1 401 Unauthorized'</span><span class="p"></span>);<span class="nv"></span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=</span><span class="s1">'Unauthorized'</span><span class="p"></span>));<span class="k">return</span><span class="p">;</span>
<span class="p"></span>}</span
<span>$employee</span> <span>=</span> <span>new</span> <span class="Employee">Employee</span>(<span>(</span><span class="int">int</span><span>)</span> <span>$cookie</span><span>-></span><span class="id_employee">id_employee</span><span>;</span>
<span>if</span> <span>(</span><span>!</span><span class="Validate">Validate</span>::<span class="isLoadedObject">isLoadedObject</span>(<span>$employee</span>)
<span>||</span> <span>!</span><span>$employee</span><span>-></span><span class="hasAuthOnShop">hasAuthOnShop</span>(<span>$this</span><span>-></span><span class="context">context</span><span>-></span><span class="shop">shop</span><span>-></span><span class="id">id</span>) <span>{</span>
<span class="header">header</span>(<span>'HTTP/1.1 403 Forbidden'</span>);<span class="/span">$this</span><span>-></span><span class="ajaxRender">ajaxRender</span>(<span>json_encode</span>([<span>'error'</span> => <span>'Insufficient permissions'</span>]));<span class="return">return</span>;<span class="/span">}</span>
<span class="c1">// 3. 엄격한 입력 유효성 검사 (Strict input validation)</span>
<span class="nv">$idProduct</span> <span class="o">=</span> <span class="p">(<span class="n">int</span>)</span> <span class="nc">Tools</span> <span class="o">::</span> <span class="nf">getValue</span> <span class="p">('id_product');</span>
<span class="nv">$newPrice</span> <span class="o">=</span> <span class="p">(<span class="n">float</span>)</span> <span class="nc">Tools</span> <span class="o">::</span> <span class="nf">getValue</span> <span class="p">('price');</span>
<span class="k">if</span> <span class="p">($idProduct <= 0) {</span>
<span class="nv">$this</span> <span class="o">-></span> <span class="nf">ajaxRender</span> <span class="p">(json_encode(['error' => '유효하지 않은 상품 ID']));</span>
<span class="k">return</span>;<span class="p"></span>
<span class="p">}</span
<span class="k">if</span> <span class="p">(</span><span class="nv">$newPrice</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">||</span> <span class="nv">$newPrice</span> <span class="o">></</span> <span class="mf">999999.99</span><span class="p"></span><span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="nf">ajaxRender</span><span class="p">(</span><span class="nb">json_encode</span><span class="p">([</span><span class="s1">'error'</span> <span class="o">=></span> <span class="s1">'Invalid price range'</span><span class="p">]));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p"></span>}
<span class="c1">// 4. 현재 상점(shop) 컨텍스트에 속하는지 제품을 확인합니다</span>
<span class="nv">$product</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Product</span><span class="p">($idProduct</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="n">context</span><span class="o">-></span><span class="n">shop</span><span class="o">-></span><span class="n">id</span><span class="p">);</span>
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기