Files
sam-manage/resources/views/academy/backend-dev.blade.php
김보곤 d5a8911fd4 refactor: [academy] 모든 페이지에서 '백과사전' 단어 제거
- 타이틀, 히어로 제목에서 중복 표현 정리
- 8개 페이지 일괄 적용
2026-02-23 13:18:08 +09:00

1283 lines
87 KiB
PHP

@extends('layouts.app')
@section('title', '백엔드 개발')
@push('styles')
<style>
.academy-img-hover {
transition: box-shadow 0.2s ease;
}
.academy-img-hover:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.academy-img-wrap {
overflow: hidden;
border-radius: 0.75rem;
}
#hover-preview {
display: none;
position: fixed;
inset: 0;
z-index: 45;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(3px);
pointer-events: none;
}
#hover-preview.is-active {
display: flex;
pointer-events: none;
}
#hover-preview img {
max-height: 80vh;
max-width: 85vw;
border-radius: 0.75rem;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
opacity: 0;
transform: scale(0.3);
transition: opacity 0.25s ease, transform 0.25s ease;
}
#hover-preview.is-active img {
opacity: 1;
transform: scale(1);
}
#hover-preview .hover-caption {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
color: rgba(255,255,255,0.85);
font-size: 0.8rem;
background: rgba(0,0,0,0.5);
padding: 0.4rem 1rem;
border-radius: 2rem;
white-space: nowrap;
opacity: 0;
transition: opacity 0.3s ease 0.15s;
}
#hover-preview.is-active .hover-caption {
opacity: 1;
}
#lightbox {
display: none;
position: fixed;
inset: 0;
z-index: 50;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(4px);
}
#lightbox.is-open {
display: flex;
}
#lightbox img {
max-height: 90vh;
max-width: 90vw;
border-radius: 0.5rem;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
}
.help-balloon-trigger {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff7ed;
color: #c2410c;
font-size: 11px;
font-weight: 700;
cursor: pointer;
border: 1.5px solid #fdba74;
vertical-align: middle;
margin-left: 4px;
transition: all 0.15s ease;
position: relative;
user-select: none;
}
.help-balloon-trigger:hover {
background: #c2410c;
color: white;
border-color: #9a3412;
}
.help-balloon {
display: none;
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) scale(0.95);
background: #1e293b;
color: #f1f5f9;
font-size: 12px;
font-weight: 400;
line-height: 1.6;
padding: 10px 14px;
border-radius: 10px;
max-width: 300px;
min-width: 200px;
width: max-content;
white-space: normal;
word-break: keep-all;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
z-index: 40;
opacity: 0;
transition: opacity 0.15s ease, transform 0.15s ease;
pointer-events: none;
}
.help-balloon::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 7px solid transparent;
border-top-color: #1e293b;
}
.help-balloon-trigger.is-active .help-balloon {
display: block;
opacity: 1;
transform: translateX(-50%) scale(1);
pointer-events: auto;
}
.help-balloon-trigger.is-active .help-balloon.balloon-right {
left: auto;
right: -10px;
transform: scale(1);
}
.help-balloon-trigger.is-active .help-balloon.balloon-right::after {
left: auto;
right: 14px;
transform: none;
}
.code-block {
background: #1e1e2e;
color: #a6e3a1;
border-radius: 0.75rem;
padding: 1rem 1.25rem;
font-family: 'Courier New', Courier, monospace;
font-size: 0.75rem;
line-height: 1.6;
overflow-x: auto;
white-space: pre;
}
.code-block .code-comment { color: #6c7086; }
.code-block .code-tag { color: #89b4fa; }
.code-block .code-attr { color: #f9e2af; }
.code-block .code-string { color: #a6e3a1; }
.code-block .code-keyword { color: #cba6f7; }
.code-block .code-func { color: #89dceb; }
.code-block .code-number { color: #fab387; }
</style>
@endpush
@section('content')
<div class="max-w-6xl mx-auto">
<!-- 히어로 배너 -->
<div class="rounded-2xl overflow-hidden mb-8 shadow-lg" style="background: linear-gradient(135deg, #431407 0%, #0f172a 100%);">
<div class="flex items-center" style="flex-wrap: wrap;">
<div style="flex: 1 1 300px; padding: 2rem 2.5rem;">
<div class="flex items-center gap-2 text-sm mb-2" style="color: #fb923c;">
<span>아카데미</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
<span style="color: #fdba74;">백엔드 개발</span>
</div>
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">백엔드 개발</h1>
<p class="text-sm" style="color: #cbd5e1;">서버, 데이터베이스, API부터 배포까지 비개발자도 이해하는 백엔드 가이드</p>
</div>
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
<div class="overflow-hidden rounded-xl">
<img src="{{ asset('images/academy/backend-dev/1.svg') }}" alt="3계층 아키텍처"
class="w-full rounded-xl cursor-pointer academy-img-hover"
onclick="openLightbox(this)">
</div>
</div>
</div>
</div>
<div class="flex gap-8">
<!-- 좌측 고정 목차 (TOC) -->
<nav class="hidden lg:block shrink-0" style="width: 220px;">
<div class="sticky top-24">
<div class="bg-orange-50 border border-orange-200 rounded-xl p-5">
<h2 class="font-semibold text-orange-800 mb-3 flex items-center gap-2 text-sm">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
목차
</h2>
<div class="space-y-0.5 text-xs">
<a href="#what-is-backend" class="block text-orange-700 hover:text-orange-900 py-1 font-medium">1. 백엔드란?</a>
<a href="#frontend-vs-backend" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">프론트 vs 백엔드</a>
<a href="#backend-roles" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">백엔드의 역할</a>
<a href="#server-client" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">2. 서버와 클라이언트</a>
<a href="#http-cycle" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">HTTP 요청/응답</a>
<a href="#url-structure" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">URL 구조</a>
<a href="#database" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">3. 데이터베이스</a>
<a href="#rdb-nosql" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">RDB vs NoSQL</a>
<a href="#sql-crud" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">SQL CRUD</a>
<a href="#laravel" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">4. Laravel</a>
<a href="#mvc-pattern" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">MVC 패턴</a>
<a href="#eloquent-orm" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">Eloquent ORM</a>
<a href="#api-design" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">5. API 설계</a>
<a href="#rest-principles" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">REST 원칙</a>
<a href="#api-auth" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">API 인증</a>
<a href="#auth-security" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">6. 인증과 보안</a>
<a href="#session-vs-token" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">세션 vs 토큰</a>
<a href="#security-threats" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">보안 위협</a>
<a href="#business-logic" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">7. 비즈니스 로직</a>
<a href="#service-layer" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">서비스 레이어</a>
<a href="#transaction" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">트랜잭션</a>
<a href="#queue-async" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">8. 큐와 비동기</a>
<a href="#sync-vs-async" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">동기 vs 비동기</a>
<a href="#queue-worker" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">/워커</a>
<a href="#caching-performance" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">9. 캐싱과 성능</a>
<a href="#cache-basics" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">캐시 원리</a>
<a href="#n-plus-one" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">N+1 문제</a>
<a href="#deployment" class="block text-orange-700 hover:text-orange-900 py-1 font-medium mt-2">10. 배포와 운영</a>
<a href="#environments" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">환경 분리</a>
<a href="#deploy-monitor" class="block text-orange-600 hover:text-orange-800 py-0.5 pl-3">Docker 배포</a>
</div>
</div>
</div>
</nav>
<!-- 우측 콘텐츠 -->
<div class="flex-1 min-w-0 space-y-10">
<!-- 모바일 목차 -->
<div class="lg:hidden bg-orange-50 border border-orange-200 rounded-xl p-4 mb-6">
<details>
<summary class="font-semibold text-orange-800 text-sm cursor-pointer flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
목차 보기
</summary>
<nav class="mt-3 space-y-1 text-sm">
<a href="#what-is-backend" class="block text-orange-700 hover:text-orange-900 py-1">1. 백엔드란?</a>
<a href="#server-client" class="block text-orange-700 hover:text-orange-900 py-1">2. 서버와 클라이언트</a>
<a href="#database" class="block text-orange-700 hover:text-orange-900 py-1">3. 데이터베이스 기초</a>
<a href="#laravel" class="block text-orange-700 hover:text-orange-900 py-1">4. Laravel 프레임워크</a>
<a href="#api-design" class="block text-orange-700 hover:text-orange-900 py-1">5. API 설계</a>
<a href="#auth-security" class="block text-orange-700 hover:text-orange-900 py-1">6. 인증과 보안</a>
<a href="#business-logic" class="block text-orange-700 hover:text-orange-900 py-1">7. 비즈니스 로직</a>
<a href="#queue-async" class="block text-orange-700 hover:text-orange-900 py-1">8. 큐와 비동기 처리</a>
<a href="#caching-performance" class="block text-orange-700 hover:text-orange-900 py-1">9. 캐싱과 성능 최적화</a>
<a href="#deployment" class="block text-orange-700 hover:text-orange-900 py-1">10. 배포와 운영</a>
</nav>
</details>
</div>
{{-- ============================================================ --}}
{{-- 1. 백엔드란? --}}
{{-- ============================================================ --}}
<section id="what-is-backend" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
백엔드란?
</h2>
<div id="frontend-vs-backend" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
프론트엔드 vs 백엔드
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">3계층(3-Tier) 아키텍처란 프레젠테이션(화면), 애플리케이션(로직), 데이터(DB) 분리하는 설계 방식이다. 계층은 독립적으로 수정·확장할 있다.</span></span>
</h3>
<div class="bg-orange-50 rounded-lg p-5 border border-orange-100 mb-4">
<p class="text-sm text-orange-900 leading-relaxed">
<strong>백엔드(Back-end)</strong> 사용자 눈에 보이지 않는 서버 영역이다.
데이터를 저장하고, 비즈니스 규칙을 처리하고, 보안을 관리하고, API를 통해 프론트엔드에 데이터를 전달하는 역할을 한다.
</p>
</div>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 공장의 생산라인</p>
<p class="text-xs text-amber-900 leading-relaxed">
백엔드는 <strong>공장의 생산라인</strong>이다. 고객은 완성된 제품(화면) 보지만,
뒤에서는 원자재(데이터) 입고, 가공(비즈니스 로직), 품질검사(유효성 검증), 출하(API 응답) 이루어진다.
프론트엔드가 매장(쇼룸)이라면, 백엔드는 뒤의 공장 전체다.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200 text-center">
<div class="mb-2">
<svg class="w-8 h-8 mx-auto text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
</div>
<p class="font-bold text-orange-800 mb-1">프론트엔드 (매장)</p>
<p class="text-orange-700">사용자가 보는 화면<br>HTML, CSS, JavaScript<br>버튼, , 메뉴, 테이블</p>
</div>
<div class="bg-gray-50 rounded-lg p-4 border text-center">
<div class="mb-2">
<svg class="w-8 h-8 mx-auto text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2" /></svg>
</div>
<p class="font-bold text-gray-800 mb-1">백엔드 (공장)</p>
<p class="text-gray-600">서버에서 처리하는 로직<br>PHP, Laravel, MySQL<br>데이터 저장, 인증, API</p>
</div>
</div>
</div>
<div id="backend-roles" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
백엔드의 4가지 역할
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<p class="font-bold text-blue-800 mb-1">1. 데이터 저장/관리</p>
<p class="text-blue-700">주문, 고객, 제품 정보를 데이터베이스에 저장하고 필요할 꺼내온다.</p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<p class="font-bold text-green-800 mb-1">2. 비즈니스 로직 처리</p>
<p class="text-green-700">견적 계산, 재고 차감, 할인 적용 업무 규칙을 코드로 구현한다.</p>
</div>
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200">
<p class="font-bold text-purple-800 mb-1">3. 보안/인증</p>
<p class="text-purple-700">로그인, 권한 확인, 비밀번호 암호화 보안을 책임진다.</p>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
<p class="font-bold text-orange-800 mb-1">4. API 제공</p>
<p class="text-orange-700">프론트엔드(, ) 데이터를 전달하는 창구를 만들어 제공한다.</p>
</div>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM 시스템의 백엔드:</strong>
SAM은 API 서버(Laravel) 백엔드를 담당한다. MNG(관리자 ) React 앱이 API를 통해 데이터를 받고,
MySQL 데이터베이스에 주문·견적·재고 모든 데이터가 저장된다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/1.svg') }}" alt="3계층 아키텍처 다이어그램"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">3계층 아키텍처 브라우저, API 서버, 데이터베이스</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 2. 서버와 클라이언트 --}}
{{-- ============================================================ --}}
<section id="server-client" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
서버와 클라이언트
</h2>
<div id="http-cycle" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
HTTP 요청과 응답
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">HTTP는 HyperText Transfer Protocol. 웹에서 데이터를 주고받는 약속(규약)이다. 브라우저가 서버에 "이것 주세요"라고 요청하면, 서버가 "여기 있습니다"라고 응답한다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 편지 주고받기</p>
<p class="text-xs text-amber-900 leading-relaxed">
클라이언트(브라우저) 서버에 <strong>편지(요청)</strong> 보낸다. 편지 봉투에는 받는 주소(URL), 요청 종류(GET/POST), 내용이 들어 있다.
서버는 편지를 읽고 처리한 , <strong>답장(응답)</strong> 보낸다. 답장에는 상태 코드(200 성공, 404 없음) 데이터가 들어 있다.
</p>
</div>
<div class="overflow-x-auto mb-4">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">HTTP 메서드</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">역할</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">비유</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-green-600">GET</td>
<td class="py-2 px-3">데이터 조회</td>
<td class="py-2 px-3">서류 열람 요청</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-blue-600">POST</td>
<td class="py-2 px-3">데이터 생성</td>
<td class="py-2 px-3"> 주문서 제출</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-orange-600">PUT</td>
<td class="py-2 px-3">데이터 전체 수정</td>
<td class="py-2 px-3">주문서 전체 다시 작성</td>
</tr>
<tr>
<td class="py-2 px-3 font-mono font-bold text-red-600">DELETE</td>
<td class="py-2 px-3">데이터 삭제</td>
<td class="py-2 px-3">주문 취소</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="url-structure" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
URL 구조와 HTTPS
</h3>
<div class="code-block mb-4"><span class="code-comment">// URL 구조 분해</span>
<span class="code-string">https</span>://<span class="code-func">api.sam-erp.com</span>/<span class="code-keyword">api/v1</span>/<span class="code-attr">orders</span>/<span class="code-number">123</span>?<span class="code-tag">status=active</span>
<span class="code-comment">// https 프로토콜 (암호화 통신)</span>
<span class="code-comment">// api.sam-erp.com 도메인 (서버 주소)</span>
<span class="code-comment">// /api/v1 API 접두사 + 버전</span>
<span class="code-comment">// /orders 자원 (Resource)</span>
<span class="code-comment">// /123 특정 자원의 ID</span>
<span class="code-comment">// ?status=active 쿼리 파라미터 (필터)</span></div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 API 통신:</strong>
SAM은 HTTPS로 암호화된 통신을 사용한다. API 서버 주소는 별도 도메인으로 운영되며,
MNG와 React 앱이 REST API를 통해 데이터를 주고받는다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/2.svg') }}" alt="요청-응답 사이클"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">요청-응답 사이클 클라이언트와 서버의 대화</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 3. 데이터베이스 기초 --}}
{{-- ============================================================ --}}
<section id="database" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
데이터베이스 기초
</h2>
<div id="rdb-nosql" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
RDB vs NoSQL
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">RDB(관계형 데이터베이스) 엑셀처럼 행과 열로 데이터를 저장한다. NoSQL은 JSON 문서, - 유연한 형태로 저장한다. SAM은 MySQL(RDB) 사용한다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 엑셀 파일</p>
<p class="text-xs text-amber-900 leading-relaxed">
관계형 데이터베이스는 <strong> 정리된 엑셀 파일</strong> 같다.
시트(테이블) 제목 (컬럼) 있고, (레코드) 데이터가 들어간다.
시트 간에 "주문번호"같은 공통 항목으로 연결(관계) 있다.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<p class="font-bold text-blue-800 mb-2">RDB (관계형)</p>
<ul class="text-blue-700 space-y-1">
<li> 행과 열로 구성된 테이블</li>
<li> SQL 언어로 데이터 조작</li>
<li> 테이블 관계(FK) 설정</li>
<li> : MySQL, PostgreSQL</li>
</ul>
</div>
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-bold text-gray-800 mb-2">NoSQL (비관계형)</p>
<ul class="text-gray-600 space-y-1">
<li> JSON 형태로 유연하게 저장</li>
<li> 대량 데이터에 강함</li>
<li> 스키마 없이 자유로운 구조</li>
<li> : MongoDB, Redis</li>
</ul>
</div>
</div>
<p class="text-sm text-gray-700 mb-3">
데이터베이스의 핵심 개념 가지: <strong>PK(Primary Key)</strong> 행을 고유하게 식별하는 번호(사원번호),
<strong>FK(Foreign Key)</strong> 다른 테이블을 참조하는 연결고리(부서코드),
<strong>인덱스</strong> 빠른 검색을 위한 색인이다.
</p>
</div>
<div id="sql-crud" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
SQL CRUD와 인덱스
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">인덱스는 특정 컬럼의 값과 위치를 미리 정리해둔 목록이다. 뒷면의 색인처럼, 전체를 훑지 않고 원하는 데이터를 빠르게 찾을 있다.</span></span>
</h3>
<div class="code-block mb-4"><span class="code-comment">-- CREATE: 데이터 생성</span>
<span class="code-keyword">INSERT INTO</span> <span class="code-func">orders</span> (customer_name, total) <span class="code-keyword">VALUES</span> (<span class="code-string">'주일산업'</span>, <span class="code-number">1500000</span>);
<span class="code-comment">-- READ: 데이터 조회</span>
<span class="code-keyword">SELECT</span> * <span class="code-keyword">FROM</span> <span class="code-func">orders</span> <span class="code-keyword">WHERE</span> status = <span class="code-string">'active'</span>;
<span class="code-comment">-- UPDATE: 데이터 수정</span>
<span class="code-keyword">UPDATE</span> <span class="code-func">orders</span> <span class="code-keyword">SET</span> status = <span class="code-string">'shipped'</span> <span class="code-keyword">WHERE</span> id = <span class="code-number">123</span>;
<span class="code-comment">-- DELETE: 데이터 삭제</span>
<span class="code-keyword">DELETE FROM</span> <span class="code-func">orders</span> <span class="code-keyword">WHERE</span> id = <span class="code-number">123</span>;</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 데이터베이스:</strong>
SAM은 MySQL 8.0 사용하며, <code>tenant_id</code> 컬럼으로 회사(테넌트) 데이터를 격리한다.
주문, 견적, 품목, 사용자 모든 데이터가 관계형 테이블에 저장된다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/3.svg') }}" alt="ERD 테이블 관계도"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">ERD 테이블 관계 주문, 주문항목, 품목</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 4. Laravel 프레임워크 --}}
{{-- ============================================================ --}}
<section id="laravel" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
Laravel 프레임워크
</h2>
<div id="mvc-pattern" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
MVC 패턴
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">미들웨어는 요청이 컨트롤러에 도달하기 전에 거치는 관문이다. 로그인 확인, 권한 검사, 요청 로깅 등을 처리한다. 공장의 보안 검색대와 같다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 공장 부서 분리</p>
<p class="text-xs text-amber-900 leading-relaxed">
<strong>Model(모델)</strong> 자재 창고 데이터를 보관하고 꺼내준다.
<strong>View()</strong> 쇼룸 완성품을 예쁘게 진열한다.
<strong>Controller(컨트롤러)</strong> 공장장 주문을 받아 창고에서 자재를 꺼내고, 가공해서, 쇼룸에 진열하도록 지시한다.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs mb-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200 text-center">
<p class="font-bold text-blue-800 mb-1">Model (모델)</p>
<p class="text-blue-700">데이터 = DB 테이블<br>데이터 조회·저장·수정</p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200 text-center">
<p class="font-bold text-green-800 mb-1">View ()</p>
<p class="text-green-700">화면 = HTML 템플릿<br>데이터를 사용자에게 표시</p>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200 text-center">
<p class="font-bold text-orange-800 mb-1">Controller (컨트롤러)</p>
<p class="text-orange-700">중재자 = 비즈니스 로직<br>요청 처리, 모델·뷰 연결</p>
</div>
</div>
<div class="code-block mb-4"><span class="code-comment">// routes/web.php URL과 컨트롤러 연결</span>
<span class="code-keyword">Route</span>::<span class="code-func">get</span>(<span class="code-string">'/orders'</span>, [<span class="code-tag">OrderController</span>::class, <span class="code-string">'index'</span>]);
<span class="code-comment">// Controller 요청을 처리하는 중재자</span>
<span class="code-keyword">class</span> <span class="code-tag">OrderController</span> {
<span class="code-keyword">public function</span> <span class="code-func">index</span>() {
$orders = <span class="code-tag">Order</span>::<span class="code-func">where</span>(<span class="code-string">'status'</span>, <span class="code-string">'active'</span>)-><span class="code-func">get</span>();
<span class="code-keyword">return</span> <span class="code-func">view</span>(<span class="code-string">'orders.index'</span>, <span class="code-func">compact</span>(<span class="code-string">'orders'</span>));
}
}</div>
</div>
<div id="eloquent-orm" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
Eloquent ORM과 Artisan
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">ORM(Object-Relational Mapping) SQL을 직접 쓰지 않고 PHP 코드로 데이터베이스를 다루는 기술이다. 통역사처럼 PHP 언어를 SQL로 자동 변환해준다.</span></span>
</h3>
<div class="code-block mb-4"><span class="code-comment">// SQL 직접 작성</span>
<span class="code-keyword">SELECT</span> * <span class="code-keyword">FROM</span> orders <span class="code-keyword">WHERE</span> status = <span class="code-string">'active'</span> <span class="code-keyword">ORDER BY</span> created_at <span class="code-keyword">DESC</span>;
<span class="code-comment">// Eloquent ORM 같은 결과를 PHP로</span>
$orders = <span class="code-tag">Order</span>::<span class="code-func">where</span>(<span class="code-string">'status'</span>, <span class="code-string">'active'</span>)
-><span class="code-func">orderBy</span>(<span class="code-string">'created_at'</span>, <span class="code-string">'desc'</span>)
-><span class="code-func">get</span>();</div>
<div class="bg-gray-50 rounded-lg p-4 border text-xs text-gray-700 mb-4">
<p class="font-semibold text-gray-800 mb-2">Artisan 주요 명령어</p>
<div class="space-y-1 font-mono">
<p><span class="text-orange-600">php artisan migrate</span> DB 테이블 생성/수정</p>
<p><span class="text-orange-600">php artisan make:controller</span> 컨트롤러 생성</p>
<p><span class="text-orange-600">php artisan make:model</span> 모델 생성</p>
<p><span class="text-orange-600">php artisan cache:clear</span> 캐시 초기화</p>
<p><span class="text-orange-600">php artisan tinker</span> 대화형 PHP 콘솔</p>
</div>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 Laravel:</strong>
SAM은 Laravel 11 + PHP 8.3 사용한다. MNG와 API 모두 Laravel 프레임워크 기반이며,
Eloquent ORM으로 MySQL 데이터를 다루고, Artisan 명령어로 마이그레이션·캐시 관리를 한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/4.svg') }}" alt="MVC 흐름도"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">MVC 흐름도 Route Controller Model View</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 5. API 설계 --}}
{{-- ============================================================ --}}
<section id="api-design" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
API 설계
</h2>
<div id="rest-principles" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
REST 원칙과 JSON
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">REST는 URL로 자원(Resource) 표현하고 HTTP 메서드로 행위를 나타내는 설계 방식이다. /orders는 주문 자원, GET은 조회, POST는 생성 행위를 의미한다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 주문 창구</p>
<p class="text-xs text-amber-900 leading-relaxed">
API는 <strong>주문 창구</strong>. 메뉴판(API 문서) 보고 정해진 양식(JSON)으로 주문(요청)하면,
주방(서버)에서 요리(처리)해서 음식(데이터) 내준다.
아무렇게나 주문하면 되고, 정해진 형식을 따라야 한다.
</p>
</div>
<div class="overflow-x-auto mb-4">
<table class="w-full text-sm border-collapse">
<thead>
<tr class="border-b-2 border-gray-200 bg-gray-50">
<th class="text-left py-2 px-3 font-medium text-gray-600">메서드</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">URL</th>
<th class="text-left py-2 px-3 font-medium text-gray-600">동작</th>
</tr>
</thead>
<tbody class="text-xs text-gray-700">
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-green-600">GET</td>
<td class="py-2 px-3 font-mono">/api/v1/orders</td>
<td class="py-2 px-3">주문 목록 조회</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-green-600">GET</td>
<td class="py-2 px-3 font-mono">/api/v1/orders/123</td>
<td class="py-2 px-3">주문 #123 상세 조회</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-blue-600">POST</td>
<td class="py-2 px-3 font-mono">/api/v1/orders</td>
<td class="py-2 px-3"> 주문 생성</td>
</tr>
<tr class="border-b border-gray-100">
<td class="py-2 px-3 font-mono font-bold text-orange-600">PUT</td>
<td class="py-2 px-3 font-mono">/api/v1/orders/123</td>
<td class="py-2 px-3">주문 #123 수정</td>
</tr>
<tr>
<td class="py-2 px-3 font-mono font-bold text-red-600">DELETE</td>
<td class="py-2 px-3 font-mono">/api/v1/orders/123</td>
<td class="py-2 px-3">주문 #123 삭제</td>
</tr>
</tbody>
</table>
</div>
<div class="code-block mb-4"><span class="code-comment">// JSON 응답 예시</span>
{
<span class="code-attr">"data"</span>: {
<span class="code-attr">"id"</span>: <span class="code-number">123</span>,
<span class="code-attr">"customer"</span>: <span class="code-string">"주일산업"</span>,
<span class="code-attr">"total"</span>: <span class="code-number">1500000</span>,
<span class="code-attr">"status"</span>: <span class="code-string">"active"</span>,
<span class="code-attr">"items"</span>: [
{ <span class="code-attr">"name"</span>: <span class="code-string">"방화셔터 A형"</span>, <span class="code-attr">"qty"</span>: <span class="code-number">5</span> }
]
}
}</div>
</div>
<div id="api-auth" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
API 인증과 버전 관리
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">Sanctum은 Laravel 공식 인증 패키지다. 모바일 앱과 SPA를 위한 토큰 발급, API 인증을 간단하게 구현한다. 회원증을 발급받아 매번 보여주는 것과 같다.</span></span>
</h3>
<div class="code-block mb-4"><span class="code-comment">// Bearer Token 인증 회원증 제시</span>
<span class="code-keyword">GET</span> /api/v1/orders
<span class="code-tag">Authorization</span>: <span class="code-string">Bearer eyJhbGciOiJIUzI1NiIs...</span>
<span class="code-comment">// 버전 관리 API가 바뀌어도 기존 앱이 동작</span>
/api/<span class="code-func">v1</span>/orders <span class="code-comment"> 현재 버전</span>
/api/<span class="code-func">v2</span>/orders <span class="code-comment"> 버전 (구조 변경)</span></div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 API 인증:</strong>
SAM API는 <code>/api/v1/</code> 접두사를 사용하고, Laravel Sanctum으로 토큰 인증을 처리한다.
React 앱에서 로그인하면 토큰을 발급받아 이후 모든 API 요청에 포함한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/5.svg') }}" alt="REST API 구조"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">REST API 구조 URL 분해와 JSON 응답</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 6. 인증과 보안 --}}
{{-- ============================================================ --}}
<section id="auth-security" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
인증과 보안
</h2>
<div id="session-vs-token" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
세션 vs 토큰
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">bcrypt는 비밀번호를 되돌릴 없는 형태로 변환(해싱)하는 알고리즘이다. 같은 비밀번호라도 매번 다른 해시값을 생성하여 보안성이 높다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 도장카드 vs 출입배지</p>
<p class="text-xs text-amber-900 leading-relaxed">
<strong>세션</strong> <strong>도장카드</strong>. 가게(서버) 도장카드를 보관해두고, 때마다 번호표(세션ID) 보여준다. 가게가 기록을 관리한다.
<strong>토큰</strong> <strong>출입배지</strong>. 내가 배지(JWT) 가지고 다니며, 입장할 때마다 배지를 보여준다. 건물(서버) 배지 진위만 확인한다.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
<p class="font-bold text-blue-800 mb-2">세션 (Session)</p>
<ul class="text-blue-700 space-y-1">
<li> 서버에 상태 저장 (Stateful)</li>
<li> 쿠키로 세션 ID 전달</li>
<li> 서버 메모리/DB 사용</li>
<li> SAM MNG에서 사용</li>
</ul>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
<p class="font-bold text-orange-800 mb-2">토큰 (Token)</p>
<ul class="text-orange-700 space-y-1">
<li> 서버에 상태 미저장 (Stateless)</li>
<li> Authorization 헤더로 전달</li>
<li> 확장성이 좋음 (서버 무상태)</li>
<li> SAM API에서 사용 (Sanctum)</li>
</ul>
</div>
</div>
</div>
<div id="security-threats" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
보안 위협과 방어
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<p class="font-bold text-red-800 mb-2">SQL Injection</p>
<p class="text-red-700 mb-2">사용자 입력에 악의적 SQL을 삽입하는 공격</p>
<p class="text-green-700">방어: 파라미터 바인딩 (Eloquent 자동 처리)</p>
</div>
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<p class="font-bold text-red-800 mb-2">CSRF</p>
<p class="text-red-700 mb-2">사용자 모르게 악의적 요청을 보내는 공격</p>
<p class="text-green-700">방어: CSRF 토큰 검증 (Laravel 자동 처리)</p>
</div>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 보안:</strong>
SAM MNG는 세션 기반 인증 + CSRF 토큰으로 보안을 처리하고,
API 서버는 Sanctum 토큰 인증을 사용한다. 비밀번호는 bcrypt로 해싱하여 저장한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/6.svg') }}" alt="세션 vs 토큰 비교"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">인증 비교 세션 방식 vs 토큰 방식</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 7. 비즈니스 로직 --}}
{{-- ============================================================ --}}
<section id="business-logic" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">7</span>
비즈니스 로직
</h2>
<div id="service-layer" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
서비스 레이어와 유효성 검증
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">서비스 레이어는 컨트롤러에서 비즈니스 로직을 분리한 클래스다. 컨트롤러가 "무엇을 할지" 결정하면, 서비스가 "어떻게 할지" 실행한다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 회사 규정</p>
<p class="text-xs text-amber-900 leading-relaxed">
비즈니스 로직은 <strong>회사 규정</strong>이다. "견적 금액이 100만원 이상이면 팀장 승인 필요",
"재고가 0이면 주문 불가" 같은 규칙을 코드로 구현한 것이다.
규정이 바뀌면 서비스 클래스만 수정하면 된다.
</p>
</div>
<div class="code-block mb-4"><span class="code-comment">// Validation 입력값 검증</span>
$request-><span class="code-func">validate</span>([
<span class="code-string">'customer_name'</span> => <span class="code-string">'required|max:100'</span>,
<span class="code-string">'email'</span> => <span class="code-string">'required|email'</span>,
<span class="code-string">'total'</span> => <span class="code-string">'required|numeric|min:0'</span>,
]);
<span class="code-comment">// Event/Listener 이벤트 기반 처리</span>
<span class="code-comment">// 주문 생성 이벤트 발생 리스너가 알림 발송</span>
<span class="code-keyword">event</span>(<span class="code-keyword">new</span> <span class="code-tag">OrderCreated</span>($order));
<span class="code-comment">// SendOrderNotification 리스너가 자동 실행</span></div>
</div>
<div id="transaction" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
트랜잭션
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">트랜잭션은 여러 DB 작업을 하나의 단위로 묶는 것이다. 모두 성공하면 확정(commit), 하나라도 실패하면 전부 취소(rollback)한다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 은행 이체</p>
<p class="text-xs text-amber-900 leading-relaxed">
A 계좌에서 10만원을 빼고 B 계좌에 10만원을 넣는 이체를 생각해보자.
A에서 빼기는 성공했는데 B에 넣기가 실패하면? 돈이 사라진다!
<strong>트랜잭션</strong> 작업을 하나로 묶어 "둘 다 성공하거나, 둘 다 취소"하도록 보장한다.
</p>
</div>
<div class="code-block mb-4"><span class="code-comment">// Laravel 트랜잭션 예시</span>
<span class="code-tag">DB</span>::<span class="code-func">transaction</span>(<span class="code-keyword">function</span> () {
<span class="code-comment">// 1. A 계좌에서 출금</span>
$accountA-><span class="code-func">decrement</span>(<span class="code-string">'balance'</span>, <span class="code-number">100000</span>);
<span class="code-comment">// 2. B 계좌에 입금</span>
$accountB-><span class="code-func">increment</span>(<span class="code-string">'balance'</span>, <span class="code-number">100000</span>);
<span class="code-comment">// 성공 자동 commit</span>
<span class="code-comment">// 하나라도 실패 자동 rollback</span>
});</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 비즈니스 로직:</strong>
SAM은 견적 계산, 주문 처리, 재고 관리 등의 비즈니스 로직을 서비스 클래스에서 처리한다.
주문 생성 재고 차감, 이력 기록 등을 트랜잭션으로 묶어 데이터 무결성을 보장한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/7.svg') }}" alt="트랜잭션 다이어그램"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">트랜잭션 성공(commit) 실패(rollback)</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 8. 큐와 비동기 처리 --}}
{{-- ============================================================ --}}
<section id="queue-async" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">8</span>
큐와 비동기 처리
</h2>
<div id="sync-vs-async" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
동기 vs 비동기
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">큐는 선입선출(FIFO) 방식의 작업 대기열이다. 은행 대기표처럼 먼저 들어온 작업부터 순서대로 처리한다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 대기표 시스템</p>
<p class="text-xs text-amber-900 leading-relaxed">
<strong>동기 처리</strong> 은행 창구에서 서는 사람이 끝날 때까지 기다려야 한다.
<strong>비동기 처리</strong> 대기표를 받는 번호표를 받고 자기 볼일을 보다가, 호출되면 처리한다.
이메일 100통을 보낼 , 동기면 30 기다려야 하지만 비동기() 즉시 "발송 예약됨" 표시된다.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<p class="font-bold text-red-800 mb-2">동기 처리 (Synchronous)</p>
<p class="text-red-700">순서대로 하나씩 처리<br> 작업 끝나야 다음 시작<br>사용자가 기다려야 </p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<p class="font-bold text-green-800 mb-2">비동기 처리 (Asynchronous)</p>
<p class="text-green-700">큐에 넣고 즉시 응답<br>워커가 백그라운드에서 처리<br>사용자는 기다리지 않음</p>
</div>
</div>
</div>
<div id="queue-worker" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
/워커와 스케줄러
</h3>
<div class="code-block mb-4"><span class="code-comment">// Job 클래스 큐에 넣을 작업 단위</span>
<span class="code-keyword">class</span> <span class="code-tag">SendAlimtalk</span> <span class="code-keyword">implements</span> <span class="code-func">ShouldQueue</span> {
<span class="code-keyword">public function</span> <span class="code-func">handle</span>() {
<span class="code-comment">// 알림톡 발송 로직</span>
<span class="code-tag">AlimtalkService</span>::<span class="code-func">send</span>($this->phone, $this->message);
}
}
<span class="code-comment">// 큐에 작업 넣기</span>
<span class="code-tag">SendAlimtalk</span>::<span class="code-func">dispatch</span>($phone, $message);
<span class="code-comment">// 즉시 응답, 워커가 나중에 처리</span>
<span class="code-comment">// 스케줄러 매일 새벽 2시에 백업</span>
$schedule-><span class="code-func">command</span>(<span class="code-string">'backup:run'</span>)-><span class="code-func">dailyAt</span>(<span class="code-string">'02:00'</span>);</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 활용:</strong>
SAM은 알림톡 발송, PDF 생성, 이메일 발송 등을 큐로 비동기 처리한다.
Docker 컨테이너 안에서 Queue Worker가 상시 실행되며, Scheduler로 정기 작업을 자동 실행한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/8.svg') }}" alt="큐 처리 흐름"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center"> 처리 요청 워커 완료</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 9. 캐싱과 성능 최적화 --}}
{{-- ============================================================ --}}
<section id="caching-performance" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">9</span>
캐싱과 성능 최적화
</h2>
<div id="cache-basics" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
캐시의 원리
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">캐시는 자주 사용하는 데이터를 빠른 저장소에 임시 보관하는 기법이다. DB에서 매번 조회하는 대신 캐시에서 바로 꺼내면 응답 속도가 수십 빨라진다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 책상 필기도구</p>
<p class="text-xs text-amber-900 leading-relaxed">
자주 쓰는 볼펜과 메모지는 <strong>책상 (캐시)</strong> 놓아둔다. 필요할 바로 꺼내 있다.
가끔 쓰는 도구는 <strong>서랍(데이터베이스)</strong> 보관한다. 서랍을 열고 찾는 시간이 걸린다.
캐시는 이처럼 자주 쓰는 데이터를 가까이 두어 빠르게 접근하는 기법이다.
</p>
</div>
<div class="code-block mb-4"><span class="code-comment">// Laravel 캐시 사용</span>
<span class="code-comment">// 캐시에 있으면 바로 반환, 없으면 DB에서 조회 캐시에 저장</span>
$orders = <span class="code-tag">Cache</span>::<span class="code-func">remember</span>(<span class="code-string">'active_orders'</span>, <span class="code-number">3600</span>, <span class="code-keyword">function</span> () {
<span class="code-keyword">return</span> <span class="code-tag">Order</span>::<span class="code-func">where</span>(<span class="code-string">'status'</span>, <span class="code-string">'active'</span>)-><span class="code-func">get</span>();
});
<span class="code-comment">// 3600 = 1시간() 동안 캐시 유지</span></div>
</div>
<div id="n-plus-one" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
N+1 문제와 Eager Loading
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">N+1 문제는 1번의 쿼리로 목록을 가져온 , 항목마다 추가 쿼리를 실행하는 것이다. 100 주문을 조회하면 101번의 쿼리가 발생한다.</span></span>
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
<p class="font-bold text-red-800 mb-2">N+1 문제 (느림)</p>
<div class="font-mono text-red-700 space-y-0.5">
<p>1. SELECT * FROM orders</p>
<p>2. SELECT * FROM items WHERE order_id=1</p>
<p>3. SELECT * FROM items WHERE order_id=2</p>
<p>... (N번 반복)</p>
<p class="mt-1 font-sans font-bold"> 101 쿼리!</p>
</div>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
<p class="font-bold text-green-800 mb-2">Eager Loading (빠름)</p>
<div class="font-mono text-green-700 space-y-0.5">
<p>1. SELECT * FROM orders</p>
<p>2. SELECT * FROM items WHERE order_id IN (1,2,...)</p>
<p class="mt-1 font-sans font-bold"> 2 쿼리!</p>
</div>
</div>
</div>
<div class="code-block mb-4"><span class="code-comment">// N+1 문제 발생</span>
$orders = <span class="code-tag">Order</span>::<span class="code-func">all</span>(); <span class="code-comment">// 1 쿼리</span>
<span class="code-keyword">foreach</span> ($orders <span class="code-keyword">as</span> $order) {
$order->items; <span class="code-comment">// N번 추가 쿼리!</span>
}
<span class="code-comment">// Eager Loading으로 해결</span>
$orders = <span class="code-tag">Order</span>::<span class="code-func">with</span>(<span class="code-string">'items'</span>)-><span class="code-func">get</span>(); <span class="code-comment">// 2 쿼리로 !</span></div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 성능 최적화:</strong>
SAM은 config, route 캐싱으로 응답 속도를 최적화하고,
Eager Loading으로 N+1 문제를 방지한다. DB 인덱스를 적절히 설정하여 대량 데이터 조회 성능을 확보한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/9.svg') }}" alt="캐시 계층 다이어그램"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">캐시 계층 캐시 히트와 캐시 미스</p>
</div>
</div>
</section>
{{-- ============================================================ --}}
{{-- 10. 배포와 운영 --}}
{{-- ============================================================ --}}
<section id="deployment" class="scroll-mt-20">
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
<span class="w-8 h-8 bg-orange-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">10</span>
배포와 운영
</h2>
<div id="environments" class="scroll-mt-20 mb-8">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
환경 분리
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon">.env 파일은 DB 접속 정보, API 환경마다 달라지는 설정을 모아둔 파일이다. 절대 Git에 커밋하면 된다.</span></span>
</h3>
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
<p class="font-semibold text-amber-800 mb-2">비유: 시험생산 품질검사 출하</p>
<p class="text-xs text-amber-900 leading-relaxed">
공장에서 신제품을 바로 출하하지 않는다.
<strong>시험생산(Local)</strong>에서 만들어보고, <strong>품질검사(Staging)</strong> 통과하면,
비로소 <strong>출하(Production)</strong>한다.
소프트웨어도 마찬가지로 환경을 분리하여 안전하게 배포한다.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs mb-4">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200 text-center">
<p class="font-bold text-blue-800 mb-1">Local (개발)</p>
<p class="text-blue-700">개발자 PC<br>자유롭게 실험<br>Docker 컨테이너</p>
</div>
<div class="bg-yellow-50 rounded-lg p-4 border border-yellow-200 text-center">
<p class="font-bold text-yellow-800 mb-1">Staging (테스트)</p>
<p class="text-yellow-700">운영과 동일한 환경<br>최종 검증<br> 데이터 없음</p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-200 text-center">
<p class="font-bold text-green-800 mb-1">Production (운영)</p>
<p class="text-green-700">실제 서비스<br> 사용자 접속<br>장애 즉시 대응</p>
</div>
</div>
</div>
<div id="deploy-monitor" class="scroll-mt-20 mb-4">
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
<span class="w-2 h-2 bg-orange-400 rounded-full"></span>
Docker 배포와 모니터링
</h3>
<div class="code-block mb-4"><span class="code-comment"># SAM 배포 절차 (간략)</span>
<span class="code-comment"># 1. 코드 작성 & 커밋</span>
<span class="code-func">git add</span> . && <span class="code-func">git commit</span> -m <span class="code-string">"feat: 새 기능 추가"</span>
<span class="code-comment"># 2. 코드 푸시</span>
<span class="code-func">git push</span> origin develop
<span class="code-comment"># 3. 서버에서 코드 받기</span>
<span class="code-func">git pull</span> origin develop
<span class="code-comment"># 4. 의존성 설치 & 마이그레이션</span>
<span class="code-func">composer install</span>
<span class="code-func">php artisan migrate</span>
<span class="code-func">php artisan config:clear</span></div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs mb-4">
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-bold text-gray-800 mb-2">Git 브랜치 전략</p>
<ul class="text-gray-600 space-y-1">
<li> <code>develop</code> 개발 진행 브랜치</li>
<li> <code>main</code> 운영 배포 브랜치</li>
<li> <code>feature/*</code> 기능 개발 브랜치</li>
<li> <code>hotfix/*</code> 긴급 수정 브랜치</li>
</ul>
</div>
<div class="bg-gray-50 rounded-lg p-4 border">
<p class="font-bold text-gray-800 mb-2">로그와 모니터링</p>
<ul class="text-gray-600 space-y-1">
<li> <code>storage/logs/laravel.log</code> 로그</li>
<li> <code>nginx error.log</code> 웹서버 로그</li>
<li> <code>php artisan queue:work</code> 상태</li>
<li> <code>docker ps</code> 컨테이너 상태</li>
</ul>
</div>
</div>
<div class="bg-orange-50 rounded-lg p-4 border border-orange-100 mb-4">
<p class="text-xs text-orange-800 leading-relaxed">
<strong>SAM의 배포:</strong>
SAM은 Docker Compose로 로컬 개발 환경(MNG, API, MySQL, Nginx) 구성하고,
운영 서버에는 직접 배포한다. develop 브랜치에서 개발을 진행하며,
코드 포맷팅(Pint), 캐시 클리어, 마이그레이션을 거쳐 배포한다.
</p>
</div>
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
<img src="{{ asset('images/academy/backend-dev/10.svg') }}" alt="배포 파이프라인"
class="rounded-lg cursor-pointer academy-img-hover"
style="max-height: 320px; width: auto;"
onclick="openLightbox(this)">
</div>
<p class="text-xs text-gray-400 mb-4 text-center">배포 파이프라인 코드 Git 빌드 서버</p>
<!-- 핵심 정리 -->
<div class="mt-6 bg-orange-50 rounded-lg p-5 border border-orange-100">
<h4 class="font-semibold text-orange-800 mb-2 text-sm">핵심 정리</h4>
<p class="text-xs text-orange-700 leading-relaxed">
백엔드는 <strong>"사용자 눈에 보이지 않는 서버의 세계"</strong>.
HTTP로 요청을 받아, 데이터베이스에서 데이터를 꺼내고, 비즈니스 로직을 처리하여 API로 응답한다.
Laravel 프레임워크가 MVC 패턴, ORM, 미들웨어, , 캐시 핵심 기능을 제공하고,
세션·토큰으로 인증, 트랜잭션으로 데이터 무결성, Eager Loading으로 성능을 보장한다.
Docker로 개발 환경을 통일하고, Git으로 버전을 관리하여 안전하게 배포한다.
SAM은 모든 백엔드 기술을 활용하여 ERP/MES 통합 시스템을 운영하고 있다.
</p>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<!-- hover 프리뷰 오버레이 -->
<div id="hover-preview">
<img id="hover-preview-img" src="" alt="">
<span class="hover-caption" id="hover-preview-caption"></span>
</div>
<!-- 클릭 라이트박스 -->
<div id="lightbox" onclick="closeLightbox()">
<button onclick="closeLightbox()" style="position:absolute; top:1rem; right:1rem; background:none; border:none; cursor:pointer; color:rgba(255,255,255,0.8); padding:0.5rem;">
<svg style="width:2rem; height:2rem;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
<img id="lightbox-img" onclick="event.stopPropagation()">
</div>
<script>
(function() {
var preview = document.getElementById('hover-preview');
var previewImg = document.getElementById('hover-preview-img');
var previewCaption = document.getElementById('hover-preview-caption');
var hoverTimer = null;
var isPreviewActive = false;
var HOVER_DELAY = 350;
document.querySelectorAll('.academy-img-hover').forEach(function(img) {
img.addEventListener('mouseenter', function() {
var el = this;
hoverTimer = setTimeout(function() {
showPreview(el);
}, HOVER_DELAY);
});
img.addEventListener('mouseleave', function() {
clearTimeout(hoverTimer);
if (isPreviewActive) {
hidePreview();
}
});
});
function showPreview(el) {
previewImg.src = el.src;
previewImg.alt = el.alt || '';
var caption = el.alt || '';
var nextP = el.parentElement && el.parentElement.querySelector('p');
if (nextP) caption = nextP.textContent;
previewCaption.textContent = caption;
preview.classList.add('is-active');
isPreviewActive = true;
}
function hidePreview() {
preview.classList.remove('is-active');
isPreviewActive = false;
}
window.openLightbox = function(el) {
var lb = document.getElementById('lightbox');
var img = document.getElementById('lightbox-img');
img.src = el.src;
img.alt = el.alt;
lb.classList.add('is-open');
document.body.style.overflow = 'hidden';
hidePreview();
};
window.closeLightbox = function() {
var lb = document.getElementById('lightbox');
lb.classList.remove('is-open');
document.body.style.overflow = '';
};
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
if (isPreviewActive) hidePreview();
closeLightbox();
}
});
// 도움말 풍선 토글
window.toggleBalloon = function(el) {
var wasActive = el.classList.contains('is-active');
// 모든 열린 풍선 닫기
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
t.classList.remove('is-active');
});
// 클릭한 풍선만 토글
if (!wasActive) {
el.classList.add('is-active');
}
};
// 풍선 외부 클릭 시 닫기
document.addEventListener('click', function(e) {
if (!e.target.closest('.help-balloon-trigger')) {
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
t.classList.remove('is-active');
});
}
});
})();
</script>
@include('components.academy-glossary', ['domain' => 'backend-dev'])
@endsection