<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>00kang_jh.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 13 Jul 2026 07:39:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>00kang_jh.log</title>
            <url>https://velog.velcdn.com/images/00kang_jh/profile/954a4395-d140-467d-9118-8ea461eb87aa/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 00kang_jh.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/00kang_jh" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[회의록 자동화 만들기 1: 회의록을 n8n 안으로 가져오기 (4편)]]></title>
            <link>https://velog.io/@00kang_jh/%ED%9A%8C%EC%9D%98%EB%A1%9D-%EC%9E%90%EB%8F%99%ED%99%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%ED%9A%8C%EC%9D%98%EB%A1%9D%EC%9D%84-n8n-%EC%95%88%EC%9C%BC%EB%A1%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-4%ED%8E%B8</link>
            <guid>https://velog.io/@00kang_jh/%ED%9A%8C%EC%9D%98%EB%A1%9D-%EC%9E%90%EB%8F%99%ED%99%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%ED%9A%8C%EC%9D%98%EB%A1%9D%EC%9D%84-n8n-%EC%95%88%EC%9C%BC%EB%A1%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-4%ED%8E%B8</guid>
            <pubDate>Mon, 13 Jul 2026 07:39:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>n8n 학습기 시리즈 — 4편. 토이 프로젝트 (1): 데이터 진입점 뚫기</strong>
개념 세 편(데이터 흐름, 표현식, 제어 흐름)을 지나 이제 실전이다. 이번 편의 목표는 단 하나, 실제 회의록이 n8n 워크플로우 안으로 들어오게 만드는 것이다.</p>
</blockquote>
<h2 id="들어가며">들어가며</h2>
<p>회의는 잘 한다. 문제는 그 다음이다. &quot;이건 누가, 언제까지&quot;까지 정해놓고도, 회의가 끝나면 결정사항이 흐지부지 흩어진다. 누군가 정리해 공유하면 다행이지만 그것도 매번 손이 간다.</p>
<p>QA를 하면서 몸에 밴 감각이 하나 있다. <strong>결정된 것이 실행에서 누락되는 지점</strong>을 못 견디는 것. 테스트 케이스가 빠지면 버그가 새듯, 회의에서 정한 액션아이템이 빠지면 일이 샌다. 그래서 이걸 자동화해보기로 했다. 지금까지 배운 n8n으로.</p>
<h2 id="무엇을-만드나">무엇을 만드나</h2>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/6408482f-fef3-4653-8ac6-74d1640320f7/image.png" alt=""></p>
<p>만들려는 파이프라인은 이렇다.</p>
<p><strong>화상 회의가 끝난다 → 회의록이 자동 저장된다 → n8n이 감지해 읽는다 → LLM이 요약과 액션아이템을 뽑는다 → 요약은 Slack으로, 액션아이템은 이슈로 자동 등록된다.</strong></p>
<p>단순한 &quot;회의 요약 봇&quot;에서 멈추지 않고, 뽑아낸 액션아이템을 담당자·기한이 있는 구조로 정리해 실제 이슈 트래커까지 자동 등록하는 것까지가 목표다. <strong>요약만 하는 봇과 실행까지 이어지는 자동화의 차이</strong>가 거기서 갈린다.</p>
<p>전체를 한 편에 담기엔 크니 이렇게 나눈다.</p>
<ul>
<li><strong>4편 (이 글)</strong> — 회의록을 n8n 안으로 가져오기</li>
<li><strong>5편</strong> — LLM으로 요약과 액션아이템 뽑기</li>
<li><strong>6편</strong> — Slack 알림 + 이슈 자동 등록으로 실행까지 연결</li>
</ul>
<h2 id="회의록은-어디서-오나">회의록은 어디서 오나</h2>
<p>데이터 소스부터. 화상 회의는 Google Meet을 쓴다. Meet은 회의 중 트랜스크립트를 켜두면, 회의가 끝난 뒤 대화 내용을 <strong>Google Docs 문서로 만들어 주최자의 Drive에 자동 저장</strong>해준다. 이게 파이프라인의 출발 데이터다.</p>
<p>두 가지를 짚어둔다. 이 자동 트랜스크립트 기능은 자격이 되는 Google Workspace 구독이 필요하다(개인 무료 Gmail로는 안 된다). 그리고 트랜스크립트는 <strong>회의를 주최한 계정의 Drive</strong>에 저장되므로, 회의는 그 계정으로 여는 게 좋다.</p>
<p>한 가지 의도적인 선택도 있다. 나는 Google이 만들어주는 AI 요약이 아니라 <strong>대화 원문 트랜스크립트</strong>를 쓴다. 요약은 5편에서 LLM에게 시킬 일이라, Google이 미리 요약해버리면 이 프로젝트의 재미가 사라진다.</p>
<h2 id="첫-번째-관문-셀프호스팅-n8n에-구글-붙이기">첫 번째 관문: 셀프호스팅 n8n에 구글 붙이기</h2>
<p>여기가 이번 편에서 제일 험한 구간이다. 실제로 4편 작업 시간의 대부분을 여기서 썼다.</p>
<p>n8n Cloud라면 &quot;Sign in with Google&quot; 버튼 하나로 끝난다. 그런데 도커로 <strong>셀프호스팅</strong>하는 경우엔 그게 안 된다. Google Cloud Console에서 <strong>직접 OAuth 앱을 만들어</strong> n8n에 연결해야 한다.</p>
<p>순서가 중요하다. 구글부터 가면 두 번 왔다갔다 하게 되니, <strong>n8n에서 리다이렉트 URL을 먼저 확보하고</strong> 구글로 넘어가는 게 낫다.</p>
<ol>
<li>n8n에서 Google Drive Trigger 노드를 추가하고, 자격증명 생성 화면을 연다. 여기 표시되는 <strong>OAuth Redirect URL</strong>을 복사한다(<code>.../rest/oauth2-credential/callback</code> 형태).</li>
<li>Google Cloud Console에서 프로젝트를 만들고, <strong>Google Drive API</strong>와 <strong>Google Docs API</strong>를 켠다.</li>
<li>OAuth 동의 화면을 구성한다. Workspace 계정이면 User Type을 <strong>Internal</strong>로 할 수 있는데, 이게 훨씬 편하다(테스트 유저 등록도, 뒤에 말할 7일 만료 문제도 없다).</li>
<li>OAuth 클라이언트 ID(Web application)를 만들고, <strong>Authorized redirect URIs에 1번에서 복사한 URL을 붙여넣는다.</strong></li>
<li>발급된 <strong>Client ID와 Client Secret</strong>을 n8n 자격증명에 입력하고 연결한다.</li>
</ol>
<p>로컬 도커 사용자에게 다행인 점 하나. <strong>공개 도메인이나 SSL, 포트포워딩이 없어도 된다.</strong> 구글이 개발 목적의 localhost 리다이렉트를 허용해주기 때문이다.</p>
<h2 id="두-번째-관문-자격증명은-서비스마다-따로다">두 번째 관문: 자격증명은 서비스마다 따로다</h2>
<p>여기서 내가 헛다리를 짚었다.</p>
<p>Drive 연결을 끝내고 뿌듯한 마음으로 Google Docs 노드를 붙였는데, 실행하자마자 이런 에러가 떴다.</p>
<pre><code>Node does not have any credentials set</code></pre><p>표현식도 제대로 걸었고 문서 ID도 미리보기에 잘 뜨는데 왜? 알고 보니 <strong>n8n에서 Google Drive와 Google Docs는 서로 다른 자격증명 타입</strong>이었다. Drive Trigger에 붙인 &quot;Google Drive account&quot;는 Docs 노드에서 쓸 수 없다. Docs는 <strong>Google Docs OAuth2 API</strong> 자격증명을 따로 요구한다.</p>
<p>당황할 필요는 없다. <strong>Google Cloud Console에서 앱을 새로 만들 필요는 없기 때문이다.</strong> 같은 OAuth 앱의 Client ID와 Secret을 그대로 다시 입력하고 로그인만 한 번 더 하면 된다. 요컨대 <strong>구글 쪽 앱은 하나, n8n 쪽 자격증명은 서비스별로 하나씩</strong>이다. 이 구분을 몰라서 &quot;분명 연결했는데 왜 없다고 하지?&quot;로 한참 헤맸다.</p>
<p>(그래서 2번 단계에서 Docs API도 미리 켜두라고 적었다. Drive API만 켜두면 로그인은 되는데 실행 시점에 API not enabled로 또 막힌다.)</p>
<h2 id="drive-트리거로-새-회의록-감지하기">Drive 트리거로 새 회의록 감지하기</h2>
<p>연결이 됐으면 트리거를 놓는다. <strong>Google Drive Trigger</strong> 노드는 지정한 폴더를 주기적으로 확인(폴링)해서 새 파일을 감지한다.</p>
<p>설정은 단순하다. Trigger On을 <strong>Changes Involving a Specific Folder</strong>로 두고, <strong>감시할 폴더</strong>를 지정하고, Watch For를 <strong>File Created</strong>로 고른다. Poll Times는 기본값(1분마다)이면 충분하다.</p>
<p>여기서 알아둘 점 몇 가지. My Drive 전체를 감시하면 아무 파일에나 반응하니 <strong>폴더를 콕 집어야</strong> 한다. 그리고 노드 안내문에 적혀 있듯 <strong>하위 폴더의 변경은 감지하지 않는다.</strong> 또 트리거가 실제로 자동 실행되려면 <strong>워크플로우가 활성화(Active) 상태여야</strong> 한다. 에디터에서 열어만 둔 상태로는 돌지 않는다. 개발 중엔 <strong>Fetch Test Event</strong> 버튼으로 최근 파일을 하나 가져와 테스트하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/0b55bc36-dc6b-414f-a66c-bd21fca57729/image.png" alt=""></p>
<h2 id="세-번째-관문-폴더엔-회의록만-있는-게-아니다">세 번째 관문: 폴더엔 회의록만 있는 게 아니다</h2>
<p>Fetch Test Event를 눌렀더니 파일 하나가 잡혔다. 그런데 열어보니 회의록이 아니었다.</p>
<pre><code class="language-json">&quot;name&quot;: &quot;...분석.docx&quot;,
&quot;mimeType&quot;: &quot;application/vnd.openxmlformats-officedocument.wordprocessingml.document&quot;</code></pre>
<p>내가 감시하도록 지정한 폴더에 예전에 넣어둔 <strong>Word(.docx) 파일</strong>이 있었던 것이다. 그리고 이건 그냥 실수가 아니라, <strong>실제 운영에서 반드시 만나는 문제</strong>다. 폴더는 회의록 전용이 아니고, 사람들은 거기에 아무 파일이나 넣는다.</p>
<p>문제가 되는 이유는 명확하다. Google Docs 노드는 <strong>네이티브 Google Docs 문서</strong>(<code>application/vnd.google-apps.document</code>)만 읽을 수 있다. Word 파일이 들어오면 그대로 터진다.</p>
<p>QA 하던 버릇이 발동했다. <strong>입력이 기대한 형식인지 먼저 검증해야 한다.</strong> 그래서 트리거 뒤에 3편에서 배운 <strong>IF 노드</strong>를 붙여, <code>mimeType</code>이 Google Docs인 아이템만 통과시키기로 했다.</p>
<pre><code>{{ $json.mimeType }}  →  is equal to  →  application/vnd.google-apps.document</code></pre><p>개념 편에서 배운 분기가 프로젝트에서 처음 실전으로 쓰이는 순간이었다. 3편의 IF가 여기서 이렇게 쓰일 줄은 그때는 몰랐다.</p>
<h2 id="회의록-내용-읽어오기">회의록 내용 읽어오기</h2>
<p>이제 감지한 파일에서 텍스트를 꺼낸다. <strong>Google Docs 노드</strong>의 Get 동작을 쓴다.</p>
<p>핵심은 <strong>Doc ID or URL</strong> 필드다. 여기에 고정값을 넣으면 안 된다. 트리거가 방금 감지한 <strong>그 파일</strong>을 읽어야 하기 때문이다. 그래서 필드를 Expression 모드로 바꾸고 이렇게 넣는다.</p>
<pre><code>{{ $json.id }}</code></pre><p>2편에서 배운 드래그앤드롭 매핑이 실전에서 처음 쓰이는 순간이다. 왼쪽 INPUT 패널에서 <code>id</code> 필드를 끌어다 놓으면 표현식이 자동 생성되고, 필드 아래 미리보기에 실제 문서 ID가 뜬다.</p>
<p>여기서 잠깐 헷갈릴 수 있다. 트리거가 넘겨주는 JSON에는 <code>permissionId</code>, <code>headRevisionId</code>, <code>thumbnailVersion</code> 등 id처럼 생긴 필드가 여럿 있다. 우리가 쓸 건 <strong>최상위의 <code>id</code></strong> 하나다. 이게 파일 자체의 고유 ID고, <code>webViewLink</code>의 <code>/document/d/</code> 뒤에 붙는 그 값과 같다.</p>
<p><strong>Execute step</strong>을 누르면 오른쪽 OUTPUT에 문서 내용이 들어온다. 노드의 <strong>Simplify</strong> 옵션을 켜두면 응답이 간소화되어 다루기 편하다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/54c3ca8c-484d-462b-be2d-256de6c4c55a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/666c0faf-dea4-4251-951d-890f1951bde1/image.png" alt=""></p>
<h2 id="여기까지의-확인">여기까지의 확인</h2>
<p>이번 편에서 만든 흐름은 이렇게 정리된다.</p>
<p><strong>Google Drive Trigger(새 파일 감지) → IF(회의록만 통과) → Google Docs(텍스트 읽기)</strong></p>
<p>실제로 회의를 한 번 하고 트랜스크립트를 켠 뒤, 그 파일이 폴더에 저장되고 n8n이 내용을 읽어오면 성공이다.</p>
<p>코드로 자동화하던 시절이라면 파일 시스템을 감시하고, OAuth 토큰을 갱신하고, Docs API를 파싱하는 코드를 직접 짰어야 할 부분이다. 그걸 노드 세 개로 대신했다. 다만 <strong>인증만큼은 &quot;노드 하나로 대신&quot;이 전혀 아니었다.</strong> 노코드 툴이라도 결국 OAuth의 복잡함은 그대로 마주해야 한다는 게 이번 편의 솔직한 소감이다.</p>
<h2 id="오늘-막혔던-것--메모">오늘 막혔던 것 / 메모</h2>
<ul>
<li><strong>자격증명이 서비스마다 별개</strong> — Drive에 연결했다고 Docs가 되는 게 아니다. 구글 쪽 OAuth 앱은 하나로 재사용하되, n8n 자격증명은 서비스마다 따로 만들어야 한다.</li>
<li><strong>API를 안 켜서 나는 에러</strong> — Drive API만 켜고 Docs API를 빠뜨리면, 연결은 되는데 실행에서 막힌다. 쓸 노드의 API는 미리 다 켜두자.</li>
<li><strong>redirect_uri_mismatch</strong> — n8n 자격증명 화면의 리다이렉트 URL과 Google Console에 등록한 URI가 한 글자라도 다르면 난다. 화면의 URL을 그대로 복사해 붙이는 게 안전하다.</li>
<li><strong>7일 뒤 연결이 풀림</strong> — OAuth 앱이 External + Testing 상태면 토큰이 약 7일 후 만료된다. Workspace 계정으로 Internal을 쓸 수 있으면 그게 편하다.</li>
<li><strong>엉뚱한 파일이 트리거를 깨움</strong> — 폴더엔 회의록만 있는 게 아니다. <code>mimeType</code> 검증은 선택이 아니라 필수다.</li>
<li><strong>트리거가 안 울림</strong> — 워크플로우가 비활성 상태이거나, 폴더 지정이 잘못됐거나 둘 중 하나였다.</li>
</ul>
<h2 id="마치며">마치며</h2>
<p><strong>셀프호스팅 n8n에 구글을 연결하고, Drive 트리거로 새 회의록을 감지해, mimeType으로 걸러낸 뒤, Docs 노드로 텍스트를 읽어온다.</strong> 인증 관문이 무겁지, 넘고 나면 흐름 자체는 노드 세 개로 단순하다.</p>
<p>이제 손에 회의 원문 텍스트가 들어왔다. 다음 5편에서는 이 텍스트를 LLM에 넣어, 요약과 결정사항, 그리고 담당자·기한이 있는 액션아이템을 <strong>구조화된 JSON</strong>으로 뽑아낸다. 2편에서 다진 표현식과 JSON 감각이 거기서 진짜로 쓰인다.</p>
<p>읽어주셔서 감사합니다. 🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[n8n 제어 흐름: 분기와 아이템 단위 실행 (3편)]]></title>
            <link>https://velog.io/@00kang_jh/n8n-%EC%A0%9C%EC%96%B4-%ED%9D%90%EB%A6%84-%EB%B6%84%EA%B8%B0%EC%99%80-%EC%95%84%EC%9D%B4%ED%85%9C-%EB%8B%A8%EC%9C%84-%EC%8B%A4%ED%96%89-3%ED%8E%B8</link>
            <guid>https://velog.io/@00kang_jh/n8n-%EC%A0%9C%EC%96%B4-%ED%9D%90%EB%A6%84-%EB%B6%84%EA%B8%B0%EC%99%80-%EC%95%84%EC%9D%B4%ED%85%9C-%EB%8B%A8%EC%9C%84-%EC%8B%A4%ED%96%89-3%ED%8E%B8</guid>
            <pubDate>Thu, 09 Jul 2026 12:51:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>n8n 학습기 시리즈 — 3편. 제어 흐름: IF, Switch, 그리고 아이템 단위 실행</strong>
1편에서 던졌던 질문, &quot;왜 데이터를 굳이 배열로 감싸지?&quot;의 진짜 답이 이번 편에 있다. 개념 파트의 마지막 편이다.</p>
</blockquote>
<h2 id="들어가며">들어가며</h2>
<p>지금까지는 데이터가 한 건일 때를 다뤘다. 1편에서 HTTP로 하나 가져오고, 2편에서 그 하나를 표현식으로 가공했다. 그런데 실제 자동화는 대개 여러 건을 다룬다. 메일 여러 통, 행 여러 개, 상담 여러 건. 게다가 조건에 따라 처리 경로가 갈린다. &quot;완료된 건과 아닌 건을 다르게 처리한다&quot; 같은 것.</p>
<p>이번 편은 그 두 가지, <strong>여러 아이템을 어떻게 도는가</strong>와 <strong>어떻게 갈래를 나누는가</strong>를 정리한다.</p>
<h2 id="아이템-단위-실행--n8n이-대신-돌려주는-for-루프">아이템 단위 실행 — n8n이 대신 돌려주는 for 루프</h2>
<p>1편에서 데이터가 항상 아이템 배열로 흐른다고 했다. 그리고 &quot;왜 배열로 감싸지?&quot;라고 물었었다. 답은 이렇다.</p>
<p><strong>n8n의 노드는 입력으로 들어온 아이템 배열의 각 아이템에 대해 자동으로 반복 실행된다.</strong></p>
<p>예를 들어 HTTP Request가 할 일 5개를 배열로 가져오면, 그 뒤에 붙은 노드는 5개 아이템 각각에 대해 한 번씩 동작한다. 내가 <code>for</code> 루프를 짜서 배열을 순회하지 않아도, 플랫폼이 각 아이템에 노드 로직을 알아서 적용해준다. 코드로 치면 이런 걸 n8n이 암묵적으로 해주는 셈이다.</p>
<pre><code class="language-javascript">// 내가 코드로 짜던 것
for (const item of items) {
  process(item);
}

// n8n에서는: 노드를 하나 놓으면 위 루프가 자동으로 돈다</code></pre>
<p>이게 1편 질문의 답이다. 데이터를 항상 배열로 두는 이유는, 노드가 &quot;한 건이든 여러 건이든 똑같은 코드로&quot; 각 아이템을 처리할 수 있게 하기 위해서다. 단건은 그냥 원소가 하나인 배열일 뿐이다.</p>
<!-- 이미지: 여러 아이템이 노드를 통과하며 각각 처리되는 모습 (INPUT n items → 노드 → OUTPUT n items) -->

<h2 id="그럼-루프-노드는-어디-있나">그럼 루프 노드는 어디 있나</h2>
<p>여기서 나는 헤맸다. &quot;여러 개를 도는 거면 루프 노드가 있어야 하는 거 아냐?&quot; 하고 찾아다녔다. 그런데 <strong>대부분의 경우 루프 노드는 필요 없다.</strong> 방금 본 것처럼 반복은 기본으로 일어나기 때문이다.</p>
<p>물론 반복을 직접 제어해야 하는 노드도 있다. 그럴 때 쓰는 게 <strong>Loop Over Items</strong> 노드다(노드를 검색하면 괄호로 <code>Split in Batches</code>라는 옛 이름이 같이 뜨는데, 같은 노드다). 이 노드는 아이템을 정해진 크기(batch size)로 잘라 한 묶음씩 처리한다. 주된 쓰임새는 <strong>API 호출 rate limit 대응</strong>이다. 한 번에 다 보내면 막히는 API에, 10개씩 보내고 잠깐 쉬고(Wait 노드), 다시 10개 보내는 식으로 조절할 때 쓴다.</p>
<p>정리하면 이렇다. 단순히 &quot;여러 개를 처리&quot;하려고 루프 노드를 넣을 필요는 없다. 그건 자동이다. 루프 노드는 &quot;몇 개씩 나눠서, 속도를 조절하며&quot; 돌려야 할 때만 꺼내면 된다.</p>
<p>한 가지 주의. 모든 노드가 자동 반복하는 건 아니다. 일부 노드는 첫 아이템만 처리하거나 전체를 한 덩어리로 다룬다(예: RSS 읽기, 특정 DB 삽입 동작, &quot;Run Once for All Items&quot; 모드의 Code 노드). 이런 예외 노드를 만나면 그때 Loop Over Items로 직접 루프를 짜야 한다. 반대로, 여러 아이템 중 첫 번째만 처리하고 싶으면 노드 Settings 탭의 <strong>Execute Once</strong>를 켜면 된다.</p>
<h2 id="갈래-나누기-1--if-노드">갈래 나누기 1 — IF 노드</h2>
<p>이제 조건 분기다. 가장 기본은 <strong>IF 노드</strong>다. 조건을 평가해서 아이템을 <strong>true 출력과 false 출력</strong> 두 갈래로 내보낸다. 코드의 <code>if / else</code>와 정확히 같다.</p>
<p>예를 들어 할 일 목록에서 &quot;완료된 것과 아닌 것&quot;을 나눈다고 하면, IF 노드에 이런 조건을 건다.</p>
<pre><code>{{ $json.completed }}  →  is true</code></pre><p>그러면 <code>completed</code>가 true인 아이템은 위쪽(true) 출력으로, 나머지는 아래쪽(false) 출력으로 흘러간다. 각 갈래에 서로 다른 노드를 붙이면 경로별로 다르게 처리할 수 있다.</p>
<p>조건은 여러 개를 조합할 수도 있다. AND로 묶으면 모든 조건이 참일 때, OR로 묶으면 하나라도 참일 때 true가 된다. 한 가지 실무 팁은, 최신 IF 노드는 타입 검증(type validation)이 엄격(strict)이라 문자열 <code>&quot;true&quot;</code>와 불린 <code>true</code>를 다르게 본다는 점이다. 값 타입이 안 맞으면 조건이 조용히 안 걸리니, 미리보기로 실제 평가 결과를 확인하는 게 안전하다.</p>
<h2 id="갈래-나누기-2--switch-노드">갈래 나누기 2 — Switch 노드</h2>
<p>IF는 갈래가 둘이다. 그런데 &quot;상태가 new면 A, processing이면 B, done이면 C&quot;처럼 셋 이상으로 나눠야 할 때가 있다. 이때 IF를 여러 개 사슬처럼 잇는 대신 <strong>Switch 노드</strong>를 쓴다.</p>
<p>Switch는 표현식 하나를 여러 값과 비교해서 각각에 맞는 갈래로 아이템을 보낸다. 코드의 <code>switch</code> 문과 같다.</p>
<pre><code>{{ $json.status }}  →  &quot;new&quot; | &quot;processing&quot; | &quot;done&quot; ...</code></pre><p>IF를 사슬로 잇는 것과 Switch의 차이를 기록해두면, IF 체인은 아이템이 매번 위에서부터 조건을 거치며 첫 매치에서 멈추는 반면, Switch는 한 번의 평가로 해당 갈래로 바로 보낸다. 캔버스도 훨씬 깔끔하다. 그래서 나는 규칙을 이렇게 잡았다. <strong>갈래가 둘이면 IF, 셋 이상이면 Switch.</strong></p>
<p>한 가지 함정도 있다. Switch는 여러 갈래 조건이 동시에 참일 때 첫 매치에서 멈추지 않고 <strong>매치되는 모든 갈래로 아이템을 보낸다.</strong> 의도한 게 아니라면 조건이 겹치지 않게 설계해야 한다.</p>
<h2 id="다시-합치기--merge-노드">다시 합치기 — Merge 노드</h2>
<p>갈래로 나눴으면 다시 합쳐야 할 때가 온다. 이때 쓰는 게 <strong>Merge 노드</strong>다. 여러 갈래에서 흘러온 아이템을 하나의 흐름으로 모은다. 분기 → 각자 처리 → 병합이라는, 코드에서 조건별로 갈라 처리한 뒤 결과를 한 리스트로 다시 모으던 패턴과 같다.</p>
<h2 id="작은-실습--여러-아이템을-if로-나눠보기">작은 실습 — 여러 아이템을 IF로 나눠보기</h2>
<p>개념을 확인해봤다. 1편 워크플로우를 살짝 바꿔서, 이번엔 여러 건을 가져온다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/54769c0f-f717-4132-b6c2-68c3a3d6305e/image.png" alt=""></p>
<ol>
<li>HTTP Request의 URL을 여러 건 반환하는 주소로 바꾼다.</li>
</ol>
<pre><code>GET https://jsonplaceholder.typicode.com/todos?_limit=5</code></pre><p>실행하면 이번엔 아이템이 5개 들어온다(1편에선 1개였다). 여기서 이미 &quot;여러 아이템이 흐른다&quot;가 눈에 보인다.</p>
<ol start="2">
<li>그 뒤에 <strong>IF 노드</strong>를 붙이고 조건을 <code>{{ $json.completed }}</code> is true로 건다.</li>
<li><strong>Execute step</strong>을 누른다.</li>
</ol>
<p>실행하면 IF 노드의 두 출력이 갈린다. 완료된 할 일은 true 쪽으로, 아직인 것은 false 쪽으로 나뉘어 각각 몇 개인지 보인다. 노드를 하나 놨을 뿐인데 5개 아이템이 각자 조건을 평가받아 두 갈래로 정리된 것이다. &quot;아이템 단위 실행 + 분기&quot;가 한 화면에서 확인되는 순간이다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/85028fd6-119e-499e-b28c-ec140314fc59/image.png" alt=""></p>
<h2 id="코드로-자동화하던-관점에서">코드로 자동화하던 관점에서</h2>
<ul>
<li>아이템 단위 실행 → 내가 짜던 <code>for (item of items)</code> 루프를 플랫폼이 암묵적으로 돌려주는 것</li>
<li>Loop Over Items → 배치 크기와 대기를 직접 제어하는 명시적 루프(주로 rate limit 대응)</li>
<li>IF 노드 → <code>if / else</code></li>
<li>Switch 노드 → <code>switch</code> 문</li>
<li>Merge 노드 → 갈라 처리한 결과를 다시 한 리스트로 모으는 것</li>
</ul>
<p>1편에서 &quot;그냥 코드가 빠르겠는데?&quot;라고 했던 감각이 여기서 한 번 더 흔들렸다. 여러 건을 도는 루프를 내가 안 짜도 된다는 것, 분기와 병합이 노드 몇 개로 눈에 보이게 정리된다는 것은, 로직의 큰 그림을 잡을 때 분명한 장점이었다. 반대로 세밀한 제어가 필요하면 언제든 Code 노드로 내려가면 되고.</p>
<h2 id="오늘-막혔던-것--메모">오늘 막혔던 것 / 메모</h2>
<ul>
<li><strong>루프 노드를 찾다 헤맴</strong> — &quot;여러 개니까 루프가 필요하겠지&quot; 하고 Loop Over Items부터 넣었다가, 대부분 자동 반복이라 필요 없다는 걸 알고 걷어냈다. 루프 노드는 배치·속도 제어가 필요할 때만.</li>
<li><strong>IF 조건이 안 걸림</strong> — 타입 검증이 엄격해서 문자열과 불린을 다르게 본다. <code>&quot;true&quot;</code>(문자열)와 <code>true</code>(불린)를 혼동하면 조건이 조용히 실패한다. 미리보기로 실제 값과 타입을 확인.</li>
<li><strong>Switch 다중 매치</strong> — 조건이 겹치면 여러 갈래로 동시에 흘러간다. 갈래를 상호 배타적으로 설계하는 걸 기본으로.</li>
</ul>
<h2 id="마치며">마치며</h2>
<p>개념 파트는 여기까지다. 세 편을 요약하면, 데이터는 아이템 배열로 흐르고(1편), 표현식으로 값을 꺼내 가공하고(2편), 아이템 단위로 자동 반복되며 IF와 Switch로 갈래를 나눈다(3편). 이 세 가지만 손에 익으면 n8n 워크플로우의 대부분은 이 위에 얹히는 조합으로 읽힌다.</p>
<p>다음 편부터는 실제 토이 프로젝트다. 지금까지 배운 트리거, 데이터 흐름, 표현식, 분기를 엮어서, 회의록에서 할 일을 자동으로 뽑아 정리하는 파이프라인을 만들어볼 생각이다. 개념이 실제 문제 앞에서 어떻게 조립되는지, 그 과정을 그대로 기록해보려 한다.</p>
<p>읽어주셔서 감사합니다. 🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[n8n에서 데이터 꺼내 쓰기: 표현식과 json  (2편)]]></title>
            <link>https://velog.io/@00kang_jh/n8n%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%BA%BC%EB%82%B4-%EC%93%B0%EA%B8%B0-%ED%91%9C%ED%98%84%EC%8B%9D%EA%B3%BC-json-2%ED%8E%B8</link>
            <guid>https://velog.io/@00kang_jh/n8n%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%BA%BC%EB%82%B4-%EC%93%B0%EA%B8%B0-%ED%91%9C%ED%98%84%EC%8B%9D%EA%B3%BC-json-2%ED%8E%B8</guid>
            <pubDate>Thu, 09 Jul 2026 12:33:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>n8n 학습기 시리즈 — 2편. 표현식과 데이터 변형</strong>
1편에서 데이터가 &quot;아이템 배열 + <code>json</code> 키&quot; 형태로 흐른다는 걸 봤다. 이번 편은 그 데이터를 실제로 꺼내 쓰고 가공하는 방법이다. 개인적으로 n8n에서 제일 헷갈리면서 제일 중요한 부분이라 시간을 들였다.</p>
</blockquote>
<h2 id="들어가며">들어가며</h2>
<p>1편의 결론은 &quot;데이터가 노드 사이를 정해진 형태로 흐른다&quot;였다. 그런데 흐르기만 해서는 소용이 없다. 앞 노드가 가져온 값을 뒤 노드에서 꺼내 쓰고, 필요하면 모양을 바꿔야 자동화가 된다. 그 연결고리가 <strong>표현식(Expression)</strong> 이다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/3e6e3329-21f1-4327-997d-a14e01fbbf1d/image.png" alt=""></p>
<p>1편에서 만든 <code>Trigger manually → HTTP Request</code> 워크플로우를 그대로 이어서 진행한다. 그때 HTTP Request가 가져온 데이터는 이거였다.</p>
<pre><code class="language-json">[
  {
    &quot;json&quot;: {
      &quot;userId&quot;: 1,
      &quot;id&quot;: 1,
      &quot;title&quot;: &quot;delectus aut autem&quot;,
      &quot;completed&quot;: false
    }
  }
]</code></pre>
<p>이 <code>title</code>, <code>id</code> 같은 값을 뒤 노드에서 어떻게 꺼내 쓰는지가 이번 편의 주제다.</p>
<h2 id="표현식이란----안의-자바스크립트">표현식이란 — <code>{{ }}</code> 안의 자바스크립트</h2>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/53f24123-ba21-488c-83b4-587bd9ce2f99/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/2640c848-7c53-4299-ab5b-176d4e619e7d/image.png" alt=""></p>
<p>n8n의 표현식은 노드 입력 필드에 직접 써넣는 작은 자바스크립트 조각이고, <code>{{ ... }}</code> 중괄호로 감싼다. 예를 들면 이런 것들이 다 표현식이다.</p>
<pre><code>{{ 2 + 3 }}
{{ &quot;Hello &quot; + &quot;World&quot; }}
{{ $json.title }}</code></pre><p>노드의 파라미터 필드는 기본적으로 &quot;고정값(fixed)&quot; 모드다. 필드 옆의 토글(중괄호 아이콘)을 누르면 &quot;표현식(expression)&quot; 모드로 바뀌고, 그때부터 <code>{{ }}</code>를 써서 값을 동적으로 계산할 수 있다. 편집기가 마지막 실행 데이터를 기준으로 <strong>실시간 미리보기</strong>를 보여줘서, 표현식이 실제로 어떤 값으로 평가되는지 즉시 확인할 수 있다. 이 미리보기가 있어서 학습이 훨씬 수월했다.</p>
<h2 id="json--현재-아이템의-데이터-꺼내기"><code>$json</code> — 현재 아이템의 데이터 꺼내기</h2>
<p>가장 많이 쓰는 패턴이다. <code>$json</code>은 <strong>현재 노드로 들어온 입력 아이템의 <code>json</code> 데이터</strong>를 가리킨다. 정확히는 <code>$input.item.json</code>의 축약형이다.</p>
<p>1편의 응답 기준으로, 필드를 이렇게 꺼낸다.</p>
<pre><code>{{ $json.title }}      // &quot;delectus aut autem&quot;
{{ $json.id }}         // 1
{{ $json.completed }}  // false</code></pre><p>중첩된 객체는 점(<code>.</code>)으로 파고들고, 배열은 대괄호로 인덱싱한다.</p>
<pre><code>{{ $json.user.email }}
{{ $json.items[0].name }}</code></pre><p>1편에서 &quot;실제 값은 <code>json</code> 키 아래에 있다&quot;고 강조했던 이유가 여기서 드러난다. <code>$json</code>이 바로 그 <code>json</code>에 대한 지름길이라, 필드명만 이어 붙이면 값에 닿는다. 코드로 치면 앞 함수가 반환한 객체에서 <code>.title</code>을 꺼내는 것과 다르지 않다.</p>
<h2 id="손으로-다-외울-필요는-없다--드래그앤드롭-매핑">손으로 다 외울 필요는 없다 — 드래그앤드롭 매핑</h2>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/aa2eb0b8-2162-4f44-be41-521d39f630b7/image.gif" alt=""></p>
<p>처음엔 이 표현식들을 다 외워야 하나 싶었는데, 아니었다. 표현식 모드에서 왼쪽 입력 데이터 패널의 필드를 <strong>드래그해서 파라미터에 떨어뜨리면 표현식이 자동으로 생성</strong>된다. <code>title</code> 필드를 끌어다 놓으면 <code>{{ $json.title }}</code>이 알아서 채워진다.</p>
<p>그래서 실무 흐름은 &quot;필드를 끌어다 놓고, 필요하면 뒤에 자바스크립트 메서드를 덧붙이는&quot; 식이 된다. IDE의 자동완성이 경로를 잡아주는 것과 비슷한 감각이라 금방 익숙해졌다.</p>
<h2 id="다른-노드-참조하기--노드이름">다른 노드 참조하기 — <code>$(&#39;노드이름&#39;)</code></h2>
<p>바로 앞 노드가 아니라, 워크플로우 중간의 특정 노드 데이터를 참조해야 할 때가 있다. 이때는 노드를 이름으로 부른다.</p>
<pre><code>{{ $(&#39;HTTP Request&#39;).item.json.userId }}</code></pre><p>여기서 주의할 점 두 가지를 기록해둔다.</p>
<ul>
<li><strong>노드 이름은 대소문자와 공백까지 정확히</strong> 써야 한다. 편집기에 표시된 이름 그대로여야 하고, 오타가 있으면 값을 못 찾는다.</li>
<li>오래된 자료에는 <code>$node[&quot;HTTP Request&quot;].json</code> 같은 문법이 자주 보인다. 이건 구버전 방식이고, 현재 공식 문서가 권장하는 건 <code>$(&#39;노드이름&#39;)</code> 형태다. 검색으로 예제를 참고할 때 이 둘이 섞여 있으니 헷갈리지 않는 게 좋다.</li>
</ul>
<p>한 가지 흥미로웠던 건 <code>.item</code>의 의미다. <code>$(&#39;HTTP Request&#39;).item</code>은 단순히 &quot;그 노드의 첫 아이템&quot;이 아니라, <strong>지금 처리 중인 현재 아이템에 대응(linked)되는 그 노드의 아이템</strong>을 가져온다. 여러 아이템이 흐를 때 짝을 맞춰주는 개념인데, 코드로 배열을 순회할 때 인덱스를 직접 맞춰주던 걸 n8n이 알아서 연결해준다고 이해했다. (여러 아이템을 다루는 이야기는 3편에서 더 자세히 다룬다.)</p>
<h2 id="setedit-fields-노드로-데이터-변형하기">Set(Edit Fields) 노드로 데이터 변형하기</h2>
<p>값을 꺼내는 것까지 봤으니, 이제 모양을 바꿔본다. 데이터 변형에 가장 기본이 되는 건 <strong>Edit Fields(옛 이름 Set)</strong> 노드다. 필드를 추가하거나, 값을 수정하거나, 필요한 필드만 남기는 노드다.</p>
<p>1편 워크플로우 뒤에 Edit Fields 노드를 붙이고, 필드를 세 개 만들어봤다.</p>
<pre><code>// 문자열 메서드 활용
titleUpper = {{ $json.title.toUpperCase() }}

// 여러 값을 조합
summary = {{ &quot;할 일 #&quot; + $json.id + &quot;: &quot; + $json.title }}

// 내장 날짜 변수
fetchedAt = {{ $now }}</code></pre><p><img src="https://velog.velcdn.com/images/00kang_jh/post/4b37a4f7-41e9-4896-9067-fe0c00da8387/image.png" alt=""></p>
<p>실행하면 출력 아이템에 세 필드가 더해진다.</p>
<pre><code class="language-json">{
  &quot;titleUpper&quot;: &quot;DELECTUS AUT AUTEM&quot;,
  &quot;summary&quot;: &quot;할 일 #1: delectus aut autem&quot;,
  &quot;fetchedAt&quot;: &quot;2026-07-09T...&quot;
}</code></pre>
<p>여기서 두 가지를 확인했다. 첫째, 표현식 안에서 <code>toUpperCase()</code> 같은 <strong>평범한 자바스크립트 문자열 메서드가 그대로 먹힌다.</strong> 둘째, <code>$now</code> 같은 n8n 내장 변수를 쓸 수 있다(<code>$now</code>는 Luxon 기반 날짜/시간 객체라, 날짜 계산에 강하다). 즉 표현식은 &quot;n8n이 데이터를 꽂아주는 자바스크립트 실행 환경&quot;에 가까웠다.</p>
<h2 id="표현식이냐-code-노드냐">표현식이냐, Code 노드냐</h2>
<p>여기까지 오니 자연스럽게 든 의문. &quot;그럼 복잡한 로직도 다 표현식에 우겨넣나?&quot; 답은 아니오였고, n8n 문서의 기준도 명확했다.</p>
<ul>
<li><strong>표현식</strong> — 이전 노드 값 참조, 가벼운 변형·계산. 파라미터 필드 안에서 로직을 값 가까이에 두고 싶을 때.</li>
<li><strong>Code 노드</strong> — 자바스크립트나 파이썬을 한 스텝으로 돌리는 전용 노드. 여러 아이템을 한꺼번에 가공하거나, 조건이 복잡하거나, 재사용 로직이 필요할 때.</li>
</ul>
<p>경험칙으로 정리하면 이렇다. <strong>한 줄로 읽히면 표현식, 함수로 빼고 싶어지면 Code 노드.</strong> 표현식에 삼항 연산자를 두세 번 중첩하기 시작하면, 그건 Code 노드로 옮기라는 신호였다. 코드에서 인라인 표현식과 별도 함수 분리를 판단하던 기준과 똑같았다.</p>
<h2 id="코드로-자동화하던-관점에서">코드로 자동화하던 관점에서</h2>
<ul>
<li><code>{{ $json.title }}</code> → 앞 함수가 반환한 객체에서 <code>.title</code> 꺼내기</li>
<li>드래그앤드롭 매핑 → IDE 자동완성이 접근 경로를 잡아주는 것</li>
<li><code>$(&#39;노드이름&#39;).item</code> → 현재 아이템에 짝지어진 특정 노드의 값. 인덱스 매칭을 플랫폼이 대신 해주는 지점</li>
<li>Edit Fields 노드 → 객체 리터럴을 만들어 필드를 매핑하는 것</li>
<li>표현식 vs Code 노드 → 인라인 한 줄이냐, 함수로 분리하냐</li>
</ul>
<p>값을 참조하고 가공하는 이 층까지 익히고 나니, 1편에서 &quot;그냥 코드가 빠르겠는데?&quot; 했던 감각이 조금 달라졌다. 값 하나 꺼내려고 파싱 코드를 쓰지 않아도 되고, 날짜 처리 라이브러리를 따로 import하지 않아도 되는 건 분명한 편의였다.</p>
<h2 id="오늘-막혔던-것--메모">오늘 막혔던 것 / 메모</h2>
<ul>
<li><strong>&quot;referenced node hasn&#39;t run yet&quot; 류 에러</strong> — 이전 노드가 아직 실행되지 않은 상태에서 그 노드를 참조하면 값을 못 가져온다. 표현식을 테스트할 땐 앞 노드부터 한 번 실행해두는 게 안전하다.</li>
<li><strong>노드 이름 오타</strong> — <code>$(&#39;http request&#39;)</code>처럼 대소문자를 틀리면 조용히 <code>undefined</code>가 된다. 편집기 표시 이름을 그대로 복사하는 습관을 들였다.</li>
</ul>
<h2 id="마치며">마치며</h2>
<p>2편의 핵심은 이렇게 요약된다. <strong><code>$json</code>으로 현재 아이템의 값을 꺼내고, <code>$(&#39;노드이름&#39;)</code>으로 다른 노드를 참조하고, Edit Fields 노드와 표현식으로 데이터를 가공한다.</strong> 그리고 한 줄을 넘어가면 Code 노드로 내려간다.</p>
<p>여기까지 오면 단일 아이템을 다루는 감은 잡힌다. 그런데 자동화는 대개 여러 건을 다루고, 조건에 따라 길이 갈린다. 다음 3편에서는 분기(IF/Switch)와, n8n 특유의 &quot;아이템 단위 실행&quot; 방식을 정리한다. 1편에서 &quot;왜 데이터를 배열로 감싸지?&quot;라고 물었던 것의 진짜 답이 거기서 나온다.</p>
<p>읽어주셔서 감사합니다. 🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[n8n을 처음 띄우고, "데이터가 흐른다"는 감각을 잡기까지 (1편)]]></title>
            <link>https://velog.io/@00kang_jh/n8n%EC%9D%84-%EC%B2%98%EC%9D%8C-%EB%9D%84%EC%9A%B0%EA%B3%A0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%ED%9D%90%EB%A5%B8%EB%8B%A4%EB%8A%94-%EA%B0%90%EA%B0%81%EC%9D%84-%EC%9E%A1%EA%B8%B0%EA%B9%8C%EC%A7%80-1%ED%8E%B8</link>
            <guid>https://velog.io/@00kang_jh/n8n%EC%9D%84-%EC%B2%98%EC%9D%8C-%EB%9D%84%EC%9A%B0%EA%B3%A0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%ED%9D%90%EB%A5%B8%EB%8B%A4%EB%8A%94-%EA%B0%90%EA%B0%81%EC%9D%84-%EC%9E%A1%EA%B8%B0%EA%B9%8C%EC%A7%80-1%ED%8E%B8</guid>
            <pubDate>Wed, 08 Jul 2026 14:09:52 GMT</pubDate>
            <description><![CDATA[<h1 id="n8n을-처음-띄우고-데이터가-흐른다는-감각을-잡기까지-1편">n8n을 처음 띄우고, &quot;데이터가 흐른다&quot;는 감각을 잡기까지 (1편)</h1>
<blockquote>
<p><strong>n8n 학습기 시리즈 — 1편. 핵심 개념: 노드, 트리거, 그리고 JSON</strong>
0편에서 왜 n8n을 배우기로 했는지 적었다. 이번 편은 실제로 띄워보고 첫 워크플로우를 뜯어보며 개념을 정리한다.</p>
</blockquote>
<h2 id="들어가며">들어가며</h2>
<p>말로만 개념을 읽는 것보다, 일단 화면을 띄워놓고 노드를 만져보는 게 빠르다고 생각했다. 그래서 이번 편은 순서가 이렇다. 먼저 n8n을 셀프호스팅으로 띄우고, 첫 워크플로우를 하나 만들어 실행해본 다음, 거기서 관찰한 걸로 핵심 개념 세 가지(노드 / 트리거 / 데이터)를 정리한다.</p>
<h2 id="일단-띄우고-본다-docker">일단 띄우고 본다 (Docker)</h2>
<p>0편에서 말했듯 처음부터 셀프호스팅으로 가기로 했다. 클라우드 버전의 실행 수 제약 없이 구조를 제대로 이해하고 싶었고, Docker 컨테이너 하나 띄우는 건 부담도 아니니까.</p>
<p>Mac에 Docker Desktop이 깔려 있다는 전제로, 터미널에 다음 두 줄이면 끝난다.</p>
<pre><code class="language-bash">docker volume create n8n_data

docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n</code></pre>
<p>첫 줄은 워크플로우와 자격 증명(Credentials)을 저장할 볼륨을 만드는 것이다. 이걸 붙여줘야 컨테이너를 내렸다 올려도 만든 워크플로우가 날아가지 않는다. 이미지 경로가 <code>docker.n8n.io/n8nio/n8n</code>인 점만 주의하면 된다(공식 레지스트리 경로다).</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/91246819-6fa6-4788-b359-238c6549b956/image.png" alt=""></p>
<p>명령이 돌면 이미지를 받아 컨테이너가 뜨고, 브라우저에서 <code>http://localhost:5678</code> 로 접속하면 편집기가 나온다. 최초 1회 계정(오너) 설정을 하고 나면 빈 캔버스가 열린다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/484ca8ed-7cd8-48ec-9929-208bff913f4e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/fa9c05d9-2eec-4821-b0ad-9a525587084c/image.png" alt=""></p>
<blockquote>
<p>참고: 최근 버전은 <code>-e N8N_RUNNERS_ENABLED=true</code> 같은 환경 변수를 권장하는데, 입문 단계에선 위 최소 구성으로도 충분하다. 프로덕션으로 갈 땐 타임존, DB(PostgreSQL) 분리 등을 붙이게 되지만 그건 한참 뒤 이야기다.</p>
</blockquote>
<h2 id="개념-1--노드-워크플로우의-기본-단위">개념 1 — 노드: 워크플로우의 기본 단위</h2>
<p>빈 캔버스에서 첫인상은 단순했다. 자동화의 각 단계가 <strong>노드(Node)</strong> 라는 상자 하나이고, 노드를 선으로 이어 흐름을 만든다. 코드로 치면 함수 하나하나를 상자로 그려놓고, 앞 함수의 리턴값을 뒤 함수 입력으로 넘기는 걸 눈에 보이게 만든 셈이다.</p>
<p>노드는 크게 두 종류로 나뉜다.</p>
<ul>
<li><strong>트리거 노드</strong> — 워크플로우를 <em>시작</em>시키는 노드. 모든 워크플로우는 트리거에서 출발한다. 캔버스에 처음 노드를 추가할 때 트리거부터 고르라고 나오는 이유다.</li>
<li><strong>일반(액션) 노드</strong> — 트리거 이후에 데이터를 <em>처리</em>하는 노드. API 호출, 데이터 변형, 조건 분기, 외부 서비스로 전송 등이 여기 속한다.</li>
</ul>
<p>이 구분이 코드와 대응이 잘 됐다. 트리거 노드는 이벤트 리스너나 진입점(<code>main</code>, 웹훅 핸들러, cron 잡)이고, 일반 노드는 그 안에서 호출되는 함수들이다.</p>
<h2 id="개념-2--트리거-무엇이-워크플로우를-깨우는가">개념 2 — 트리거: 무엇이 워크플로우를 깨우는가</h2>
<p>트리거 종류를 훑어보니 익숙한 것들이었다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/fd17f43a-e7f0-4eb9-94c1-4c1a35db519b/image.png" alt=""></p>
<ul>
<li><strong>Trigger manually</strong> — 편집기에서 버튼을 눌러 수동 실행. 개발 중 테스트용이다.</li>
<li><strong>On a schedule</strong> — 정해진 주기로 실행. 사실상 cron이다.</li>
<li><strong>On webhook call</strong> — 외부에서 HTTP 요청이 들어오면 실행. 내가 API 서버에서 웹훅 핸들러 붙이던 것과 같다.</li>
<li><strong>On app event</strong> — Gmail에 새 메일이 오면, Google Drive에 새 파일이 생기면 같은 서비스별 이벤트.</li>
</ul>
<p>결국 &quot;무엇이 이 자동화를 깨우는가&quot;를 고르는 것이고, 코드로 자동화할 때 내가 매번 직접 만들던 진입 조건(스케줄러 등록, 웹훅 엔드포인트 개설, 폴링 루프)을 플랫폼이 노드 하나로 대신 제공하는 구조였다. 0편에서 &quot;매번 새로 만들던 뼈대를 플랫폼이 깔아준다&quot;고 기대했던 게 이 지점에서 처음 체감됐다.</p>
<h2 id="개념-3--데이터는-json으로-흐른다-이번-편의-핵심">개념 3 — 데이터는 JSON으로 흐른다 (이번 편의 핵심)</h2>
<p>여기가 제일 중요하다. n8n을 이해하는 열쇠는 <strong>노드 사이를 흐르는 데이터의 모양</strong>을 아는 것이다.</p>
<p>n8n에서 노드 간 데이터는 항상 <strong>아이템(item)들의 배열</strong>로 흐른다. 그리고 각 아이템은 이런 형태다.</p>
<pre><code class="language-json">[
  {
    &quot;json&quot;: {
      &quot;id&quot;: 1,
      &quot;title&quot;: &quot;예시 데이터&quot;
    }
  },
  {
    &quot;json&quot;: {
      &quot;id&quot;: 2,
      &quot;title&quot;: &quot;다음 아이템&quot;
    }
  }
]</code></pre>
<p>핵심을 정리하면 이렇다.</p>
<ul>
<li>모든 노드는 <strong>아이템 배열을 입력으로 받아, 아이템 배열을 출력으로 내보낸다.</strong></li>
<li>실제 데이터는 각 아이템의 <code>json</code> 키 안에 들어 있다. (파일 같은 바이너리는 별도로 <code>binary</code> 키에 담긴다.)</li>
<li>그래서 다음 노드에서 앞 노드의 값을 참조할 때 <code>json</code>을 거쳐 접근하게 된다. (구체적인 표현식 문법은 2편에서 다룬다.)</li>
</ul>
<p>처음엔 &quot;왜 데이터를 굳이 배열로 감싸고, 또 <code>json</code> 키로 한 번 더 감싸지?&quot; 싶었다. 그런데 이유는 금방 납득됐다. 자동화는 대부분 <strong>여러 건</strong>을 다룬다. 메일 10통, 행 100개, 파일 여러 개. 데이터를 항상 배열로 취급하면, 노드는 &quot;한 건이든 여러 건이든 똑같은 방식으로&quot; 처리할 수 있다. 코드로 치면 단일 객체와 리스트를 분기 처리하지 않고, 처음부터 전부 리스트로 두고 <code>map</code>을 도는 것과 같은 발상이다.</p>
<h2 id="첫-워크플로우-데이터를-눈으로-확인하기">첫 워크플로우: 데이터를 눈으로 확인하기</h2>
<p>개념을 글로 읽었으니 실제로 확인해봤다. 가장 단순한 조합이다.</p>
<p><strong>Trigger manually → HTTP Request</strong></p>
<ol>
<li>캔버스에서 트리거로 <strong>Trigger manually</strong>를 추가한다. (추가하면 노드 이름이 <code>When clicking &#39;Execute workflow&#39;</code>로 표시된다.)</li>
<li>그 뒤에 <strong>HTTP Request</strong> 노드를 잇고, Method는 <code>GET</code>, URL에 테스트용 공개 API를 넣는다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/ec11fcc0-41b3-40b6-9304-ddb1032185f5/image.png" alt=""></p>
<pre><code>GET https://jsonplaceholder.typicode.com/todos/1</code></pre><ol start="3">
<li>노드의 <strong>Execute step</strong> 버튼을 누른다. (워크플로우 전체를 돌릴 땐 <strong>Execute workflow</strong>를 쓴다.)</li>
</ol>
<p>실행하면 HTTP Request 노드에 결과가 뜨는데, 여기서 처음으로 &quot;데이터가 흐른다&quot;는 게 눈에 보인다. 노드 출력 패널을 보면 응답이 이렇게 담겨 있다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/63174a09-8e80-4c3f-9ce7-16594f71e31f/image.png" alt=""></p>
<pre><code class="language-json">[
  {
    &quot;json&quot;: {
      &quot;userId&quot;: 1,
      &quot;id&quot;: 1,
      &quot;title&quot;: &quot;delectus aut autem&quot;,
      &quot;completed&quot;: false
    }
  }
]</code></pre>
<p>앞서 정리한 그대로다. API 응답이 아이템 배열의 <code>json</code> 키 안에 들어와 있다. n8n UI에서는 이 출력을 Table(표), JSON, Schema 세 가지 뷰로 볼 수 있는데, 특히 Schema 뷰가 다음 노드에서 어떤 필드를 참조할 수 있는지 보여줘서 유용했다.</p>
<p>여기까지가 1편의 목표였다. HTTP Request 한 줄로 외부 데이터를 가져오는 건 코드로 수백 번 해봤지만, 그 결과가 <strong>정해진 형태(아이템 배열 + json 키)로 캔버스 위를 흐르고, 다음 노드가 그걸 받아 쓸 수 있다</strong>는 감각이 n8n의 출발점이었다.</p>
<h2 id="코드로-자동화하던-관점에서">코드로 자동화하던 관점에서</h2>
<p>정리하면서 계속 코드와 겹쳐 봤다.</p>
<ul>
<li><strong>Manual Trigger</strong> → 스크립트를 직접 실행하는 것(<code>main()</code> 호출)</li>
<li><strong>HTTP Request 노드</strong> → <code>fetch</code> / <code>axios</code> 한 줄</li>
<li><strong>노드를 잇는 선</strong> → 앞 함수의 리턴값을 뒤 함수 인자로 넘기는 것</li>
<li><strong>아이템 배열</strong> → API 응답을 리스트로 받아 <code>map</code>으로 순회하는 것</li>
</ul>
<p>여기까지 보면 &quot;그냥 코드가 더 빠르겠는데?&quot;라는 생각도 들었다. 실제로 단발성 스크립트라면 그 말이 맞다. 다만 n8n의 값어치는 이 다음부터 나온다고 본다. 인증을 노드가 관리하고, 스케줄과 웹훅이 트리거로 붙고, 실패 시 재시도가 얹히는 순간, 내가 매번 짜던 &quot;주변 코드&quot;가 사라진다. 그 대조를 다음 편들에서 계속 확인해볼 생각이다.</p>
<h2 id="오늘-막혔던-것--메모">오늘 막혔던 것 / 메모</h2>
<ul>
<li>처음에 노드 출력이 배열로 감싸여 있어서 잠깐 헷갈렸다. → n8n은 <strong>항상 아이템 배열 단위</strong>로 데이터를 다룬다는 걸 이해하니 풀렸다.</li>
<li>실제 값이 <code>json</code> 키 아래에 있다는 걸 놓치면, 나중에 표현식에서 필드를 잘못 참조하게 된다. 이건 2편에서 제대로 정리할 예정.</li>
</ul>
<h2 id="마치며">마치며</h2>
<p>1편의 결론은 한 문장이다. <strong>n8n에서 데이터는 아이템 배열의 형태로, 각 값은 <code>json</code> 키 안에 담겨 노드 사이를 흐른다.</strong> 이 구조만 손에 익으면 나머지는 그 위에 얹히는 이야기다.</p>
<p>다음 2편에서는 이 데이터를 실제로 꺼내 쓰고 변형하는 방법, 즉 표현식(<code>{{ $json }}</code>)과 노드 간 데이터 참조를 정리한다. 개인적으로 여기가 n8n에서 제일 헷갈리면서도 제일 중요한 부분이라 생각해서, 시간을 들여 다뤄보려 한다.</p>
<p>읽어주셔서 감사합니다. 🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드로 자동화하던 QA 엔지니어가 n8n을 배우기로 했다 (0편)]]></title>
            <link>https://velog.io/@00kang_jh/%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EB%8D%98-QA-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EA%B0%80-n8n%EC%9D%84-%EB%B0%B0%EC%9A%B0%EA%B8%B0%EB%A1%9C-%ED%96%88%EB%8B%A4-0%ED%8E%B8</link>
            <guid>https://velog.io/@00kang_jh/%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EB%8D%98-QA-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EA%B0%80-n8n%EC%9D%84-%EB%B0%B0%EC%9A%B0%EA%B8%B0%EB%A1%9C-%ED%96%88%EB%8B%A4-0%ED%8E%B8</guid>
            <pubDate>Mon, 06 Jul 2026 15:17:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>n8n 학습기 시리즈 — 0편. 왜 n8n인가</strong>
토이 프로젝트에 들어가기 전, 개념부터 하나씩 정리하며 남기는 기록입니다.</p>
</blockquote>
<h2 id="들어가며">들어가며</h2>
<p>2년 남짓 소프트웨어 품질 검증과 테스트 자동화를 해왔다. Playwright와 TypeScript로 E2E 테스트를 짜고, JUnit과 Maven으로 DB 동작을 검증하고, 반복되는 시나리오를 스크립트로 돌려왔다. 내 일의 상당 부분은 결국 <strong>&quot;사람이 손으로 반복하던 걸 코드가 대신하게 만드는 것&quot;</strong> 이었다.</p>
<p>그런 내가 요즘 n8n이라는 워크플로우 자동화 툴을 붙잡고 있다. 처음 마음먹었을 때 스스로도 좀 이상했다. 자동화라면 코드로 다 짜봤는데, 굳이 노코드 툴을 왜?</p>
<p>이 글은 그 질문에 대한 내 나름의 답이자, 앞으로 이어질 학습 기록의 0편이다. 배운 걸 정리하기 전에, 왜 배우기로 했는지부터 적어두려 한다.</p>
<h2 id="코드-자동화와-업무-자동화-사이의-간극">코드 자동화와 업무 자동화 사이의 간극</h2>
<p>테스트 자동화에는 명확한 대상이 있다. 로그인 폼, API 응답, 쿼리 결과. 검증할 게 정해져 있고, 기대와 다르면 빨간불이 켜진다. 이 세계는 익숙하고, 나름의 방법론도 몸에 배어 있다.</p>
<p>그런데 일을 하다 보면 &quot;테스트&quot;가 아닌 자동화가 계속 눈에 밟혔다.</p>
<ul>
<li>회의가 끝나면 회의록을 정리해서 공유하기</li>
<li>어딘가에서 데이터를 긁어와 시트에 정리하기</li>
<li>특정 이벤트가 발생하면 알림 보내기</li>
<li>API 두어 개를 엮어서 중간 데이터를 흘려보내기</li>
</ul>
<p>하나하나 뜯어보면 코드로 못 할 건 없다. 실제로 몇 개는 파이썬 스크립트로 만들어 쓰기도 했다. 그런데 매번 스크립트를 짜고, 인증 붙이고, 돌릴 환경을 마련하고, 실패하면 로그를 뒤지는 과정이 배보다 배꼽이 컸다. <strong>자동화 자체보다 자동화를 굴러가게 만드는 주변 작업이 더 무거웠다.</strong></p>
<p>이 지점에서 워크플로우 자동화 툴이 뭘 해결해주는지 궁금해졌다. 트리거, 인증, 스케줄링, 재시도 같은 &quot;매번 새로 만들던 뼈대&quot;를 플랫폼이 대신 깔아준다면, 나는 로직에만 집중하면 되는 것 아닌가.</p>
<h2 id="n8n이-뭐길래">n8n이 뭐길래</h2>
<p>먼저 정확히 짚고 넘어가자. n8n을 검색하면 &quot;오픈소스 자동화 툴&quot;이라는 소개가 흔하게 나오는데, 엄밀히 말하면 <strong>OSI 기준의 오픈소스는 아니다.</strong> n8n은 Sustainable Use License라는 fair-code(source-available) 라이선스를 따른다. 소스는 공개되어 있고, 개인이나 내부 업무 용도로는 자유롭게 셀프호스팅해서 무료로 쓸 수 있다. 다만 이걸 기반으로 상업용 SaaS를 만들어 남에게 재판매하는 건 별도 라이선스가 필요하다.</p>
<p>이 구분을 처음부터 정확히 알아둔 이유가 있다. 나중에 사업이나 제품을 고민할 때, &quot;무료 오픈소스인 줄 알았는데 아니었다&quot;는 건 꽤 뼈아픈 착오가 되기 때문이다.</p>
<p>기술적으로 n8n의 핵심은 이렇게 정리된다.</p>
<ul>
<li><strong>노드 기반 워크플로우 에디터</strong> — 자동화의 각 단계가 하나의 &quot;노드&quot;이고, 노드를 선으로 연결해 데이터 흐름을 만든다. Zapier의 선형적인 &quot;트리거 → 액션&quot; 모델과 달리, 분기·루프·병렬 실행·서브워크플로우를 기본으로 지원한다.</li>
<li><strong>실행(execution) 단위 과금</strong> — 워크플로우가 아무리 여러 단계로 복잡해도 한 번 도는 걸 1 execution으로 센다. 단계마다 과금하는 task 방식과 대조되는 지점이다.</li>
<li><strong>셀프호스팅</strong> — Docker로 내 인프라 위에 직접 띄울 수 있다. 데이터가 내 서버 밖으로 나가지 않는다.</li>
<li><strong>HTTP Request / Webhook 노드</strong> — 네이티브 연동이 없는 서비스라도 API만 있으면 직접 붙일 수 있다.</li>
<li><strong>Code 노드</strong> — 필요하면 JavaScript나 Python 코드를 워크플로우 중간에 그대로 실행할 수 있다.</li>
</ul>
<p>마지막 두 항목이 내 눈길을 끌었다. 노코드 툴이라고 해서 코드를 못 쓰게 막는 게 아니라, <strong>필요한 순간에 코드로 내려갈 수 있는 탈출구</strong>를 열어둔 구조다.</p>
<h2 id="왜-하필-n8n인가">왜 하필 n8n인가</h2>
<p>Zapier, Make, n8n. 흔히 비교되는 세 가지 중에서 n8n을 고른 이유는 결국 내 배경 때문이다.</p>
<p>Zapier는 비개발자도 몇 분 만에 자동화를 만들 수 있는 게 강점이다. 대신 코드를 넣는 순간 실행 시간·메모리·호출 횟수에 빡빡한 제약이 걸린다. 반면 n8n은 애초에 기술 유저를 겨냥해 설계됐다. Code 노드에서 로직을 온전히 돌릴 수 있고, HTTP Request로 어떤 API든 직접 호출하고, 셀프호스팅하면 내 인프라의 일부처럼 동작한다.</p>
<p>정리하면 이렇다. 나는 이미 API 인증, HTTP, JSON 데이터 다루기, 스크립트 짜기가 익숙하다. 그렇다면 <strong>그 익숙함이 제약이 아니라 무기가 되는 도구</strong>를 고르는 게 맞다. n8n에서 낯선 건 노드 기반 UI라는 패러다임 정도이고, 나머지 상당수는 내가 코드로 하던 것의 다른 표현일 거라고 봤다.</p>
<p>한 가지 더. n8n은 2.0(2026년 1월)부터 LangChain 기반 AI 노드와 AI Agent 노드를 정식으로 품었다. Claude, OpenAI, Gemini 같은 모델을 워크플로우 안에서 도구처럼 부릴 수 있다. 자동화에 LLM을 얹는 흐름을 제대로 실험해보기에도 좋은 시점이라고 판단했다.</p>
<h2 id="이-시리즈에서-다룰-것">이 시리즈에서 다룰 것</h2>
<p>거창한 강의는 아니다. 매일 조금씩 배우면서, 막혔던 지점과 그걸 어떻게 넘었는지를 위주로 남길 생각이다. 대략 이런 순서를 잡고 있다.</p>
<ul>
<li><strong>0편 (이 글)</strong> — 왜 n8n인가</li>
<li><strong>1편</strong> — 핵심 개념: 노드, 트리거, 그리고 데이터가 JSON으로 흐른다는 감각</li>
<li><strong>2편</strong> — 표현식과 데이터 변형: <code>{{ $json }}</code> 문법과 노드 간 데이터 참조</li>
<li><strong>3편</strong> — 제어 흐름: 분기(IF/Switch)와 아이템 단위 실행이라는 n8n 특유의 방식</li>
<li><strong>4편~</strong> — 실제 토이 프로젝트: 트리거 → 처리 → 실행까지 이어지는 워크플로우 만들기</li>
</ul>
<p>특히 코드로 자동화하던 관점에서 &quot;이건 코드로 하던 그거네&quot;, &quot;이건 오히려 코드가 편한데?&quot;, &quot;아 이래서 노코드가 필요하구나&quot; 같은 대조를 놓치지 않고 기록하려 한다. 같은 걸 다른 각도에서 보는 게 이 시리즈의 색깔이 될 것 같다.</p>
<h2 id="마치며">마치며</h2>
<p>배운 걸 글로 정리하는 습관은 예전 SDET 학습기를 쓰면서 효과를 봤다. 손으로 정리하지 않으면 &quot;돌아가는 걸 봤다&quot;에서 멈추고, 정작 왜 그렇게 되는지는 흐릿하게 남는다.</p>
<p>그래서 이번에도 토이 프로젝트에 뛰어들기 전에, 기초를 다지는 과정부터 차근차근 남겨보려 한다. 다음 글에서는 n8n을 직접 띄우고 첫 워크플로우를 뜯어보며 핵심 개념을 정리할 예정이다.</p>
<p>읽어주셔서 감사합니다. 🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Playwright 테스트 실행 옵션 가이드]]></title>
            <link>https://velog.io/@00kang_jh/Playwright-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%ED%96%89-%EC%98%B5%EC%85%98-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@00kang_jh/Playwright-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%8B%A4%ED%96%89-%EC%98%B5%EC%85%98-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Thu, 22 Jan 2026 02:57:43 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>이전 글에서 Playwright 설치를 완료했습니다. 이번 글에서는 설치 시 생성된 기본 예제 코드를 활용해서 다양한 테스트 실행 옵션을 알아보겠습니다.</p>
<blockquote>
<p>💻 <strong>작성 환경</strong></p>
<ul>
<li>macOS Tahoe 26.2</li>
<li>VS Code</li>
<li>Node.js</li>
</ul>
</blockquote>
<hr>
<h2 id="기본-예제-코드-살펴보기">기본 예제 코드 살펴보기</h2>
<p>Playwright 설치 시 <code>tests/example.spec.ts</code> 파일이 자동으로 생성됩니다.</p>
<pre><code class="language-typescript">import { test, expect } from &#39;@playwright/test&#39;;

test(&#39;has title&#39;, async ({ page }) =&gt; {
  await page.goto(&#39;https://playwright.dev/&#39;);

  // Expect a title &quot;to contain&quot; a substring.
  await expect(page).toHaveTitle(/Playwright/);
});

test(&#39;get started link&#39;, async ({ page }) =&gt; {
  await page.goto(&#39;https://playwright.dev/&#39;);

  // Click the get started link.
  await page.getByRole(&#39;link&#39;, { name: &#39;Get started&#39; }).click();

  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole(&#39;heading&#39;, { name: &#39;Installation&#39; })).toBeVisible();
});</code></pre>
<p>이 코드는 Playwright 공식 사이트에 접속해서 타이틀을 확인하고, &quot;Get started&quot; 링크를 클릭하는 간단한 테스트입니다. 이 예제를 기준으로 다양한 실행 옵션을 살펴보겠습니다.</p>
<hr>
<h2 id="테스트-실행-옵션">테스트 실행 옵션</h2>
<h3 id="기본-실행">기본 실행</h3>
<pre><code class="language-bash">npx playwright test</code></pre>
<p>모든 테스트 파일을 실행합니다. 기본적으로 headless 모드(브라우저 창 없이)로 실행되며, 설정된 모든 브라우저(Chromium, Firefox, WebKit)에서 테스트가 진행됩니다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/7ea6495e-ce33-4037-8861-68e46a9bd001/image.png" alt=""></p>
<h3 id="worker-수-지정">Worker 수 지정</h3>
<pre><code class="language-bash">npx playwright test --workers 3</code></pre>
<p>병렬로 실행할 worker 수를 지정합니다. 기본값은 CPU 코어 수의 절반입니다. 테스트가 많을 때 worker 수를 늘리면 전체 실행 시간을 단축할 수 있습니다. 반대로 리소스가 부족하거나 테스트 간 충돌이 발생하면 worker 수를 줄여볼 수 있습니다.</p>
<h3 id="특정-파일-실행">특정 파일 실행</h3>
<pre><code class="language-bash">npx playwright test one.spec.ts</code></pre>
<p>특정 테스트 파일만 실행합니다. 파일명 일부만 입력해도 매칭되는 파일을 찾아 실행합니다.</p>
<pre><code class="language-bash">npx playwright test one.spec.ts two.spec.ts</code></pre>
<p>여러 파일을 지정하려면 공백으로 구분해서 나열하면 됩니다.</p>
<h3 id="파일명-키워드로-실행">파일명 키워드로 실행</h3>
<pre><code class="language-bash">npx playwright test one two</code></pre>
<p>파일명에 <code>one</code> 또는 <code>two</code>가 포함된 모든 테스트 파일을 실행합니다. 확장자 없이 키워드만 입력해도 됩니다.</p>
<h3 id="테스트-이름으로-실행">테스트 이름으로 실행</h3>
<pre><code class="language-bash">npx playwright test -g &quot;test title&quot;</code></pre>
<p><code>-g</code> 옵션을 사용하면 테스트 이름(title)으로 필터링할 수 있습니다. 예제 코드 기준으로 <code>&quot;has title&quot;</code> 테스트만 실행하고 싶다면 다음과 같이 입력합니다.</p>
<pre><code class="language-bash">npx playwright test -g &quot;has title&quot;</code></pre>
<p>정규식 패턴도 사용 가능합니다.</p>
<h3 id="특정-브라우저에서-실행">특정 브라우저에서 실행</h3>
<pre><code class="language-bash">npx playwright test --project=chromium</code></pre>
<p>특정 브라우저에서만 테스트를 실행합니다. <code>playwright.config.js</code>에 정의된 프로젝트 이름을 사용합니다.</p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>--project=chromium</code></td>
<td>Chrome 기반 브라우저</td>
</tr>
<tr>
<td><code>--project=firefox</code></td>
<td>Firefox</td>
</tr>
<tr>
<td><code>--project=webkit</code></td>
<td>Safari 기반 브라우저</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/471db6df-f8d8-4a62-8d37-959a3ea386a4/image.png" alt=""></p>
<h3 id="브라우저-창-띄워서-실행">브라우저 창 띄워서 실행</h3>
<pre><code class="language-bash">npx playwright test --headed</code></pre>
<p>기본적으로 Playwright는 headless 모드로 실행되어 브라우저 창이 보이지 않습니다. <code>--headed</code> 옵션을 사용하면 실제 브라우저 창이 열리면서 테스트가 진행되는 모습을 눈으로 확인할 수 있습니다.</p>
<h3 id="디버그-모드-실행">디버그 모드 실행</h3>
<pre><code class="language-bash">npx playwright test --debug</code></pre>
<p>디버그 모드로 실행합니다. Playwright Inspector가 열리면서 테스트를 단계별로 실행하고, 각 단계에서 멈춰서 상태를 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/b6140b43-278c-4df5-8f43-e873f4972656/image.png" alt=""></p>
<h3 id="특정-라인부터-디버그">특정 라인부터 디버그</h3>
<pre><code class="language-bash">npx playwright test one.spec.ts:11 --debug</code></pre>
<p>파일명 뒤에 <code>:라인번호</code>를 붙이면 해당 라인에 있는 테스트부터 디버그 모드로 실행합니다. 특정 테스트만 집중적으로 디버깅할 때 유용합니다.</p>
<hr>
<h2 id="옵션-조합-예시">옵션 조합 예시</h2>
<p>여러 옵션을 조합해서 사용할 수 있습니다.</p>
<pre><code class="language-bash"># Chromium에서 headed 모드로 특정 파일 실행
npx playwright test example.spec.ts --project=chromium --headed

# worker 1개로 순차 실행하면서 디버그
npx playwright test --workers 1 --debug

# 특정 테스트만 Firefox에서 실행
npx playwright test -g &quot;has title&quot; --project=firefox</code></pre>
<hr>
<h2 id="실행-옵션-요약">실행 옵션 요약</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>npx playwright test</code></td>
<td>전체 테스트 실행</td>
</tr>
<tr>
<td><code>npx playwright test --workers 3</code></td>
<td>worker 수 지정</td>
</tr>
<tr>
<td><code>npx playwright test one.spec.ts</code></td>
<td>특정 파일 실행</td>
</tr>
<tr>
<td><code>npx playwright test one two</code></td>
<td>키워드로 파일 필터링</td>
</tr>
<tr>
<td><code>npx playwright test -g &quot;test title&quot;</code></td>
<td>테스트 이름으로 필터링</td>
</tr>
<tr>
<td><code>npx playwright test --project=chromium</code></td>
<td>특정 브라우저 실행</td>
</tr>
<tr>
<td><code>npx playwright test --headed</code></td>
<td>브라우저 창 띄워서 실행</td>
</tr>
<tr>
<td><code>npx playwright test --debug</code></td>
<td>디버그 모드 실행</td>
</tr>
<tr>
<td><code>npx playwright test file.spec.ts:11 --debug</code></td>
<td>특정 라인부터 디버그</td>
</tr>
</tbody></table>
<hr>
<h2 id="마치며">마치며</h2>
<p>테스트를 작성하다 보면 전체 테스트를 돌리기보다 특정 테스트만 반복 실행하는 경우가 많습니다. 오늘 다룬 옵션들을 활용하면 개발 중에 빠르게 피드백을 받으면서 테스트를 작성할 수 있습니다.</p>
<p>다음 글에서는 테스트 코드를 직접 작성하는 방법을 알아보겠습니다.</p>
<hr>
<blockquote>
<p>📚 <strong>참고 자료</strong></p>
<ul>
<li><a href="https://playwright.dev/docs/running-tests">Playwright - Running Tests</a></li>
<li><a href="https://playwright.dev/docs/test-cli">Playwright - Command Line</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Playwright 설치 가이드(js)]]></title>
            <link>https://velog.io/@00kang_jh/Playwright-%EC%84%A4%EC%B9%98-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@00kang_jh/Playwright-%EC%84%A4%EC%B9%98-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Thu, 22 Jan 2026 01:23:15 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>이전 글에서 Playwright가 무엇인지, 왜 사용하는지 알아봤습니다. 이번 글에서는 실제로 Playwright를 설치하고 첫 테스트를 실행하는 과정을 다뤄보겠습니다.</p>
<p>Node.js와 VS Code가 이미 설치되어 있다는 가정 하에 진행합니다.</p>
<blockquote>
<p>💻 <strong>작성 환경</strong></p>
<ul>
<li>macOS Tahoe 26.2</li>
<li>VS Code</li>
<li>Node.js</li>
</ul>
</blockquote>
<hr>
<h2 id="프로젝트-폴더-생성">프로젝트 폴더 생성</h2>
<p>먼저 Playwright 프로젝트를 위한 폴더를 생성하고 이동합니다.</p>
<pre><code class="language-bash">mkdir my-playwright-project
cd my-playwright-project</code></pre>
<p>VS Code에서 해당 폴더를 열어주세요.</p>
<pre><code class="language-bash">code .</code></pre>
<hr>
<h2 id="playwright-설치">Playwright 설치</h2>
<p>VS Code에서 터미널을 열고 다음 명령어를 실행합니다.</p>
<pre><code class="language-bash">npm init playwright@latest</code></pre>
<p>명령어를 실행하면 몇 가지 질문이 나타납니다.</p>
<h3 id="설치-옵션-선택">설치 옵션 선택</h3>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/182660a4-40c9-4610-a766-70d82bf4ef03/image.png" alt=""></p>
<p><strong>1. TypeScript 또는 JavaScript 선택</strong></p>
<pre><code>Do you want to use TypeScript or JavaScript?
  ▸ TypeScript
    JavaScript</code></pre><p>원하는 언어를 선택합니다. 이 가이드에서는 TypeScript를 기준으로 진행합니다.</p>
<p><strong>2. 테스트 폴더 경로</strong></p>
<pre><code>Where to put your end-to-end tests?
  ▸ tests</code></pre><p>테스트 파일이 저장될 폴더명입니다. 기본값인 <code>tests</code>를 그대로 사용하면 됩니다.</p>
<p><strong>3. GitHub Actions 워크플로우 추가 여부</strong></p>
<pre><code>Add a GitHub Actions workflow?
  ▸ false</code></pre><p>Github Actions에서 CI/CD 설정이 필요하면 <code>true</code>를 선택합니다. 지금은 <code>false</code>로 진행해도 괜찮습니다.</p>
<p><strong>4. 브라우저 설치 여부</strong></p>
<pre><code>Install Playwright browsers?
  ▸ true</code></pre><p>반드시 <code>true</code>를 선택합니다. Playwright가 사용할 브라우저 바이너리(Chromium, Firefox, WebKit)를 설치합니다. <code>false</code>를 할 경우 나중에 직접 브러우저를 설치해줘야하는 불편함이 있으니깐 웬만하면 <code>true</code>로 하시길 권장합니다.</p>
<hr>
<h2 id="설치-완료-확인">설치 완료 확인</h2>
<p>설치가 완료되면 프로젝트에 다음 파일들이 생성됩니다.</p>
<p align="center">
  <img src="https://velog.velcdn.com/images/00kang_jh/post/c3968e95-ff2c-4525-b404-35ce7ed52d57/image.png" width="50%" />
</p>


<p><code>package.json</code>을 열어보면 Playwright가 devDependencies에 추가된 것을 확인할 수 있습니다.</p>
<p align="center">
  <img src="https://velog.velcdn.com/images/00kang_jh/post/9dc0d368-cc88-4cab-b306-9bc2ae2b1970/image.png" width="50%" />
</p>


<hr>
<h2 id="제대로-설치되었는지-확인">제대로 설치되었는지 확인</h2>
<p>설치가 완료되면 다음 명령어로 Playwright 버전을 확인할 수 있습니다.</p>
<pre><code class="language-bash">npx playwright --version</code></pre>
<p>버전 정보가 출력되면 설치가 정상적으로 완료된 것입니다.
<img src="https://velog.velcdn.com/images/00kang_jh/post/066413b8-fe05-43e0-bc4e-77b5c061c692/image.png" alt=""></p>
<hr>
<h2 id="마치며">마치며</h2>
<p>터미널 명령어 몇 줄이면 Playwright 설치가 완료됩니다. 다음 글에서는 실제 테스트를 실행하고 코드를 작성하는 방법을 알아보겠습니다.</p>
<hr>
<blockquote>
<p>📚 <strong>참고 자료</strong></p>
<ul>
<li><a href="https://playwright.dev/docs/intro">Playwright - Installation</a></li>
<li><a href="https://playwright.dev/docs/running-tests">Playwright - Running Tests</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Playwright, E2E 테스트 자동화 소개]]></title>
            <link>https://velog.io/@00kang_jh/Playwright-E2E-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%8F%99%ED%99%94-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@00kang_jh/Playwright-E2E-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%90%EB%8F%99%ED%99%94-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Thu, 22 Jan 2026 00:52:26 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>웹 애플리케이션을 개발하다 보면 &quot;이 기능이 제대로 동작하는지 어떻게 확인하지?&quot;라는 고민을 하게 됩니다. 버튼을 클릭했을 때 페이지가 잘 이동하는지, 폼을 제출했을 때 데이터가 정상적으로 저장되는지, 매번 수동으로 확인하기엔 시간도 부족하고 실수할 가능성도 높죠.</p>
<p>이런 문제를 해결해주는 것이 바로 <strong>E2E(End-to-End) 테스트 자동화</strong>입니다. 그리고 오늘 소개할 <strong>Playwright</strong>는 이 영역에서 빠르게 주목받고 있는 도구입니다.</p>
<hr>
<h2 id="e2e-테스트란">E2E 테스트란?</h2>
<p>E2E 테스트는 사용자 관점에서 애플리케이션의 전체 흐름을 검증하는 테스트입니다.</p>
<p>예를 들어 쇼핑몰에서 &quot;로그인 → 상품 검색 → 장바구니 담기 → 결제&quot;까지의 과정이 정상적으로 동작하는지 확인하는 것이죠. 단위 테스트가 함수 하나하나를 검증한다면, E2E 테스트는 실제 사용자가 경험하는 시나리오 전체를 검증합니다.</p>
<p><strong>왜 필요할까요?</strong></p>
<ul>
<li>개별 컴포넌트는 정상이어도 통합했을 때 문제가 생길 수 있습니다</li>
<li>배포 전 주요 기능이 동작하는지 빠르게 확인할 수 있습니다</li>
<li>수동 테스트에 드는 반복적인 시간을 줄여줍니다</li>
</ul>
<hr>
<h2 id="playwright-소개">Playwright 소개</h2>
<p><strong>Playwright</strong>는 Microsoft에서 개발한 오픈소스 E2E 테스트 프레임워크입니다. 2020년에 처음 공개되었고, 빠른 속도와 안정성으로 Cypress, Selenium과 함께 대표적인 E2E 테스트 도구로 자리잡았습니다.</p>
<pre><code class="language-bash"># 설치
npm init playwright@latest</code></pre>
<p>설치 명령어 하나로 프로젝트 구조, 설정 파일, 예제 테스트까지 자동으로 생성해줍니다.</p>
<hr>
<h2 id="playwright의-주요-특징">Playwright의 주요 특징</h2>
<h3 id="1-크로스-브라우저-지원">1. 크로스 브라우저 지원</h3>
<p>Chromium, Firefox, WebKit 세 가지 브라우저 엔진을 단일 API로 제어할 수 있습니다. 하나의 테스트 코드로 Chrome, Safari, Firefox에서 모두 동작을 검증할 수 있다는 의미입니다.</p>
<pre><code class="language-javascript">// playwright.config.js
projects: [
  { name: &#39;chromium&#39;, use: { ...devices[&#39;Desktop Chrome&#39;] } },
  { name: &#39;firefox&#39;, use: { ...devices[&#39;Desktop Firefox&#39;] } },
  { name: &#39;webkit&#39;, use: { ...devices[&#39;Desktop Safari&#39;] } },
]</code></pre>
<h3 id="2-auto-waiting">2. Auto-waiting</h3>
<p>테스트 자동화에서 가장 골치 아픈 문제 중 하나가 타이밍입니다. 버튼이 아직 로딩 중인데 클릭하려고 하면 테스트가 실패하죠. Playwright는 요소가 클릭 가능한 상태가 될 때까지 자동으로 기다려줍니다.</p>
<pre><code class="language-javascript">// 별도의 wait 없이도 안정적으로 동작
await page.click(&#39;button#submit&#39;);</code></pre>
<h3 id="3-강력한-디버깅-도구">3. 강력한 디버깅 도구</h3>
<p>테스트가 실패했을 때 원인을 찾기 쉽도록 다양한 도구를 제공합니다.</p>
<ul>
<li><strong>Trace Viewer</strong>: 테스트 실행 과정을 스크린샷, DOM 스냅샷, 네트워크 요청과 함께 타임라인으로 확인</li>
<li><strong>Codegen</strong>: 브라우저에서 직접 조작하면 해당 동작을 코드로 자동 생성</li>
<li><strong>UI Mode</strong>: 테스트를 시각적으로 실행하고 각 단계를 확인</li>
</ul>
<pre><code class="language-bash"># Codegen 실행
npx playwright codegen https://example.com</code></pre>
<h3 id="4-네트워크-제어">4. 네트워크 제어</h3>
<p>API 요청을 가로채서 원하는 응답으로 대체하거나, 네트워크 상태를 시뮬레이션할 수 있습니다. 백엔드 없이도 프론트엔드 테스트가 가능해집니다.</p>
<pre><code class="language-javascript">await page.route(&#39;**/api/users&#39;, route =&gt; {
  route.fulfill({
    status: 200,
    body: JSON.stringify([{ id: 1, name: &#39;Test User&#39; }])
  });
});</code></pre>
<h3 id="5-병렬-실행">5. 병렬 실행</h3>
<p>테스트를 여러 워커에서 동시에 실행하여 전체 테스트 시간을 크게 단축할 수 있습니다.</p>
<hr>
<h2 id="playwright의-장점">Playwright의 장점</h2>
<h3 id="빠른-실행-속도">빠른 실행 속도</h3>
<p>브라우저와 직접 통신하는 방식을 사용해 Selenium 대비 체감될 정도로 빠릅니다. 특히 병렬 실행을 기본 지원하기 때문에 테스트 수가 많아져도 전체 실행 시간을 효율적으로 관리할 수 있습니다.</p>
<h3 id="안정적인-테스트">안정적인 테스트</h3>
<p>Auto-waiting과 자동 재시도(retry) 기능 덕분에 &quot;어제는 됐는데 오늘은 안 되는&quot; 불안정한 테스트(flaky test)가 줄어듭니다. 이건 실무에서 정말 큰 장점입니다.</p>
<h3 id="낮은-진입-장벽">낮은 진입 장벽</h3>
<p>Codegen으로 브라우저 조작을 녹화해서 코드를 생성할 수 있어, 처음 접하는 분들도 빠르게 테스트를 작성할 수 있습니다. 공식 문서도 잘 정리되어 있고 예제가 풍부합니다.</p>
<h3 id="활발한-생태계">활발한 생태계</h3>
<p>Microsoft의 지원 아래 꾸준히 업데이트되고 있고, VS Code 확장 프로그램도 제공합니다. GitHub Actions 등 CI/CD 파이프라인과의 연동도 간편합니다.</p>
<hr>
<h2 id="마치며">마치며</h2>
<p>E2E 테스트 자동화를 시작하려는 분들에게 Playwright는 좋은 선택지입니다. 설정이 간단하고, 디버깅 도구가 강력하며, 무엇보다 안정적으로 동작합니다.</p>
<p>다음 글에서는 실제로 간단한 테스트를 작성하고 실행하는 과정을 다뤄보겠습니다.</p>
<hr>
<blockquote>
<p>📚 <strong>참고 자료</strong></p>
<ul>
<li><a href="https://playwright.dev/">Playwright 공식 문서</a></li>
<li><a href="https://github.com/microsoft/playwright">Playwright GitHub</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🚀 AWS S3 이미지 업로드]]></title>
            <link>https://velog.io/@00kang_jh/AWS-S3-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C</link>
            <guid>https://velog.io/@00kang_jh/AWS-S3-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%97%85%EB%A1%9C%EB%93%9C</guid>
            <pubDate>Sat, 02 Aug 2025 06:07:02 GMT</pubDate>
            <description><![CDATA[<h1 id="🚀-aws-s3-이미지-업로드-구현-가이드">🚀 AWS S3 이미지 업로드 구현 가이드</h1>
<p>일반적으로 많은 프로젝트들에서 기본적으로 이미지 업로드 하는 기능들이 많이 들어갑니다.
그래서  AWS 기반으로 <strong>S3 + CloudFront + Presigned URL</strong> 조합을 사용하면 빠르고
안전하며 비용 효율적인 이미지 업로드를 구현할 수 있습니다.</p>
<hr>
<h2 id="1️⃣-이미지-업로드-아키텍처">1️⃣ 이미지 업로드 아키텍처</h2>
<p><strong>흐름</strong>  </p>
<ol>
<li>클라이언트 → 서버: 업로드 요청</li>
<li>서버 → AWS: Presigned URL 생성</li>
<li>서버 → 클라이언트: Presigned URL 전달</li>
<li>클라이언트 → S3: 이미지 직접 업로드</li>
<li>서버 → DB: 업로드 정보(경로, 파일명, 유저) 저장</li>
<li>조회 시: <strong>CloudFront URL</strong> 통해 이미지 접근</li>
</ol>
<blockquote>
<p>✅ 장점: 서버 부하 최소화 + 빠른 CDN 조회 + 보안 강화</p>
</blockquote>
<hr>
<h2 id="2️⃣-spring-boot-구현">2️⃣ Spring Boot 구현</h2>
<h3 id="🔹-presigned-url-생성">🔹 Presigned URL 생성</h3>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class S3Service {
    private final AmazonS3 amazonS3;
    private final String bucketName = &quot;demo-images&quot;;

    public String generatePresignedUrl(String fileName) {
        Date expiration = new Date(System.currentTimeMillis() + 1000 * 60 * 5); // 5분 유효
        GeneratePresignedUrlRequest request =
            new GeneratePresignedUrlRequest(bucketName, fileName)
            .withMethod(HttpMethod.PUT)
            .withExpiration(expiration);
        return amazonS3.generatePresignedUrl(request).toString();
    }
}</code></pre>
<h3 id="🔹-클라이언트-업로드-react-native-예시">🔹 클라이언트 업로드 (React Native 예시)</h3>
<pre><code class="language-javascript">await axios.put(presignedUrl, file, {
  headers: { &quot;Content-Type&quot;: file.type }
});</code></pre>
<hr>
<h2 id="3️⃣-cloudfront--s3-구조">3️⃣ CloudFront + S3 구조</h2>
<ul>
<li><strong>S3 (Private)</strong>: 이미지 저장</li>
<li><strong>CloudFront</strong>: 전 세계 CDN 캐싱</li>
<li><strong>OAC(Origin Access Control)</strong>: S3 직접 접근 차단</li>
<li><strong>Signed URL(Optional)</strong>: 민감한 이미지 접근 제어
<img src="blob:https://velog.io/8a7be4c6-642e-494b-bf20-0186ec45d62e" alt="업로드중.."></li>
</ul>
<p><em>(예시 다이어그램: S3 + CloudFront + Presigned URL 구조)</em></p>
<hr>
<h2 id="4️⃣-보안-체크리스트">4️⃣ 보안 체크리스트</h2>
<p>업로드 서비스는 <strong>XSS·파일 위장·대용량 업로드</strong>에 취약할 수 있습니다.<br>아래 보안 전략을 적용하면 안전합니다.</p>
<ul>
<li><input disabled="" type="checkbox"> 허용 확장자만 업로드 (<code>jpg, png, webp</code>)</li>
<li><input disabled="" type="checkbox"> MIME 타입 검증 (<code>image/</code> 로 시작 여부)</li>
<li><input disabled="" type="checkbox"> 파일 크기 제한 (1~2MB)</li>
<li><input disabled="" type="checkbox"> UUID 기반 파일명 (사용자 입력 이름 사용 ❌)</li>
<li><input disabled="" type="checkbox"> S3 Private + Presigned URL 사용</li>
<li><input disabled="" type="checkbox"> CloudFront OAC 설정으로 직접 접근 차단</li>
<li><input disabled="" type="checkbox"> 필요 시 Signed URL or JWT 인증</li>
<li><input disabled="" type="checkbox"> 업로드 후 Lambda로 이미지 포맷 검증</li>
</ul>
<hr>
<h2 id="5️⃣-비용-최적화">5️⃣ 비용 최적화</h2>
<ul>
<li><strong>프리티어 사용 시</strong>  <ul>
<li>S3 5GB / CloudFront 1TB / 20,000 GET / 2,000 PUT 무료</li>
</ul>
</li>
<li><strong>월 사용량 예시 (300명 × 5장/일 × 1MB)</strong>  <ul>
<li>45GB 저장, 45,000 PUT → 월 $2~3 수준</li>
</ul>
</li>
<li><strong>비용 절감 방법</strong>  <ul>
<li>업로드 전 <strong>리사이징/압축</strong>  </li>
<li>오래된 파일 <strong>S3 Glacier로 이동</strong></li>
</ul>
</li>
</ul>
<hr>
<h2 id="6️⃣-최종-정리">6️⃣ 최종 정리</h2>
<blockquote>
<p><strong>S3 + CloudFront + Presigned URL</strong><br>구조를 적용하면 서버 부하를 최소화하고,<br>보안·속도·비용 모든 면에서 효율적인 구조를 만들 수 있습니다.</p>
</blockquote>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[미션 DAY4 (워밍업 클럽 3기)]]></title>
            <link>https://velog.io/@00kang_jh/%EB%AF%B8%EC%85%98-DAY4-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-3%EA%B8%B0</link>
            <guid>https://velog.io/@00kang_jh/%EB%AF%B8%EC%85%98-DAY4-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-3%EA%B8%B0</guid>
            <pubDate>Fri, 07 Mar 2025 12:29:57 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-day-4-코드-리팩토링--solid-원칙-정리">📌 Day 4: 코드 리팩토링 &amp; SOLID 원칙 정리</h1>
<p>이번 미션에서는 <strong>더 읽기 좋은 코드</strong>를 만들기 위해 기존 코드를 리팩토링하고, <strong>SOLID 원칙</strong>을 자기만의 언어로 정리하는 작업을 수행했다.  </p>
<hr>
<h2 id="✅-미션-수행-목표">✅ <strong>미션 수행 목표</strong></h2>
<h3 id="🔹-1-코드-리팩토링">🔹 1. 코드 리팩토링</h3>
<ul>
<li>[섹션 3. 논리, 사고의 흐름]에서 이야기하는 내용을 중심으로 리팩토링  </li>
<li>코드의 가독성을 높이고, 유지보수를 쉽게 만들기  </li>
</ul>
<h3 id="🔹-2-solid-원칙-정리">🔹 2. SOLID 원칙 정리</h3>
<ul>
<li>SOLID 원칙을 이해하고, 이를 나만의 언어로 정리  </li>
</ul>
<hr>
<h1 id="📌-1️⃣-코드-리팩토링-과정">📌 1️⃣ 코드 리팩토링 과정</h1>
<h3 id="🕒-step-1-기존-코드-분석">🕒 <strong>[Step 1] 기존 코드 분석</strong></h3>
<p>다음은 주어진 <code>validateOrder</code> 메서드의 기존 코드이다.  </p>
<p>📌 <strong>기존 코드</strong>  </p>
<pre><code class="language-java">public boolean validateOrder(Order order) {
    if (order.getItems().size() == 0) {
        log.info(&quot;주문 항목이 없습니다.&quot;);
        return false;
    } else {
        if (order.getTotalPrice() &gt; 0) {
            if (!order.hasCustomerInfo()) {
                log.info(&quot;사용자 정보가 없습니다.&quot;);
                return false;
            } else {
                return true;
            }
        } else if (!(order.getTotalPrice() &gt; 0)) {
            log.info(&quot;올바르지 않은 총 가격입니다.&quot;);
            return false;
        }
    }
    return true;
}</code></pre>
<p>🔍 <strong>분석 결과</strong>  </p>
<ul>
<li><strong><code>getter</code>를 직접 호출하는 부분이 많음</strong> → 캡슐화를 해치고 있음  </li>
<li><strong>부정 연산자(<code>!</code>, <code>!=</code>) 사용이 많음</strong> → 코드의 직관성이 떨어짐  </li>
<li><strong>중첩된 조건문이 많음</strong> → 가독성이 낮음  </li>
</ul>
<hr>
<h3 id="🕒-step-2-리팩토링-방향-설정">🕒 <strong>[Step 2] 리팩토링 방향 설정</strong></h3>
<p>리팩토링 시 고려할 사항:<br>✔️ <code>getter</code> 사용을 최소화하기 위해 <strong>Order 내부에 메서드를 추가</strong><br>✔️ <code>부정 연산자(!)</code>를 제거하여 가독성을 높임<br>✔️ <strong>불필요한 else 문을 제거</strong>하여 코드 흐름을 단순화  </p>
<hr>
<h3 id="🕒-step-3-order-클래스-개선">🕒 <strong>[Step 3] Order 클래스 개선</strong></h3>
<p><code>Order</code> 클래스 내부에 필요한 메서드를 추가하여 <strong>객체 스스로 판단할 수 있도록 수정</strong>  </p>
<p>📌 <strong>수정된 Order 클래스</strong>  </p>
<pre><code class="language-java">import java.util.List;

public class Order {

  private final List&lt;Integer&gt; items;
  private final int totalPrice;
  private final int customer;

  public Order(List&lt;Integer&gt; items, int totalPrice, int customer) {
    this.items = items;
    this.totalPrice = totalPrice;
    this.customer = customer;
  }

  public boolean isEmpty() {
    return items.isEmpty();
  }

  public boolean hasCustomerInfo() {
    return customer != 0;
  }

  public boolean moreThanZeroTotalPrice() {
    return totalPrice &gt; 0;
  }
}
</code></pre>
<hr>
<h3 id="🕒-step-4-리팩토링된-validateorder-메서드">🕒 <strong>[Step 4] 리팩토링된 <code>validateOrder</code> 메서드</strong></h3>
<p>리팩토링된 <code>validateOrder</code>는 <strong>더 직관적이고 읽기 쉬운 코드</strong>가 되었다.  </p>
<p>📌 <strong>수정된 validateOrder 메서드</strong>  </p>
<pre><code class="language-java">public class MissionDay4 {

  public boolean validateOrder(Order order) {
    if (order.isEmpty()) {
      log.info(&quot;주문 항목이 없습니다.&quot;);
      return false;
    }

    if (order.moreThanZeroTotalPrice()) {
      if (order.hasCustomerInfo()) {
        return true;
      }
      log.info(&quot;사용자 정보가 없습니다.&quot;);
      return false;
    }

    log.info(&quot;올바르지 않은 총 가격입니다.&quot;);
    return false;
  }
}

</code></pre>
<hr>
<h2 id="🔍-리팩토링-결과-정리">🔍 <strong>리팩토링 결과 정리</strong></h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>리팩토링 전</th>
<th>리팩토링 후</th>
</tr>
</thead>
<tbody><tr>
<td><strong>getter 사용</strong></td>
<td>직접 호출 (<code>order.getTotalPrice()</code>)</td>
<td>Order 내부에서 메서드 제공 (<code>order.moreThanZeroTotalPrice()</code>)</td>
</tr>
<tr>
<td><strong>부정 연산자</strong></td>
<td><code>!order.hasCustomerInfo()</code> 사용</td>
<td><code>order.hasCustomerInfo()</code>로 긍정 표현</td>
</tr>
<tr>
<td><strong>조건문 구조</strong></td>
<td>중첩된 <code>if-else</code> 다수 포함</td>
<td>단순한 <code>if</code> 구조로 개선</td>
</tr>
<tr>
<td><strong>가독성</strong></td>
<td>흐름 파악이 어려움</td>
<td>더 직관적인 코드</td>
</tr>
</tbody></table>
<hr>
<h1 id="📌-2️⃣-solid-원칙-정리">📌 2️⃣ SOLID 원칙 정리</h1>
<p><strong>SOLID 원칙</strong>은 객체 지향 프로그래밍에서 <strong>유지보수성과 확장성을 높이기 위한 설계 원칙</strong>이다.<br>아래는 내가 이해한 SOLID 원칙을 나만의 언어로 정리한 내용이다.  </p>
<h3 id="✅-1-단일-책임-원칙-srp-single-responsibility-principle">✅ <strong>1. 단일 책임 원칙 (SRP, Single Responsibility Principle)</strong></h3>
<p><strong>&quot;하나의 클래스는 하나의 책임만 가져야 한다.&quot;</strong>  </p>
<ul>
<li>한 클래스가 너무 많은 역할을 맡으면 <strong>변경이 어려워짐</strong>  </li>
<li>하나의 책임만 있도록 <strong>기능을 나누는 것이 중요</strong>  </li>
</ul>
<h3 id="✅-2-개방-폐쇄-원칙-ocp-openclosed-principle">✅ <strong>2. 개방-폐쇄 원칙 (OCP, Open/Closed Principle)</strong></h3>
<p><strong>&quot;확장은 가능하지만, 기존 코드는 수정하지 않는다.&quot;</strong>  </p>
<ul>
<li>새로운 기능이 필요하면 <strong>기존 코드를 수정하는 것이 아니라, 확장해야 함</strong>  </li>
<li>인터페이스, 추상 클래스를 활용하면 유연한 코드 작성 가능  </li>
</ul>
<h3 id="✅-3-리스코프-치환-원칙-lsp-liskov-substitution-principle">✅ <strong>3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)</strong></h3>
<p><strong>&quot;하위 클래스는 상위 클래스를 대체할 수 있어야 한다.&quot;</strong>  </p>
<ul>
<li>부모 클래스를 상속받은 자식 클래스가 <strong>부모처럼 동작해야 함</strong>  </li>
<li>그렇지 않으면 다형성을 활용할 수 없음  </li>
</ul>
<h3 id="✅-4-인터페이스-분리-원칙-isp-interface-segregation-principle">✅ <strong>4. 인터페이스 분리 원칙 (ISP, Interface Segregation Principle)</strong></h3>
<p><strong>&quot;필요한 기능만 포함된 인터페이스를 사용하라.&quot;</strong>  </p>
<ul>
<li>한 개의 큰 인터페이스보다는, <strong>작은 단위의 인터페이스를 여러 개 만드는 것이 좋음</strong>  </li>
<li>필요하지 않은 기능까지 구현하지 않아도 됨  </li>
</ul>
<h3 id="✅-5-의존-역전-원칙-dip-dependency-inversion-principle">✅ <strong>5. 의존 역전 원칙 (DIP, Dependency Inversion Principle)</strong></h3>
<p><strong>&quot;구체적인 것보다, 추상적인 것에 의존하라.&quot;</strong>  </p>
<ul>
<li>클래스가 다른 클래스를 직접 참조하는 것이 아니라, <strong>인터페이스나 추상 클래스를 통해 의존</strong>  </li>
<li>변경에 유연한 구조를 만들 수 있음  </li>
</ul>
<hr>
<h2 id="📌-출처">📌 출처</h2>
<p>📖 <strong>강의 제목:</strong> <a href="https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95">Readable Code: 읽기 좋은 코드를 작성하는 사고법</a><br>👩‍🏫 <strong>강사:</strong> 박우빈  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQLD 공부 (2)]]></title>
            <link>https://velog.io/@00kang_jh/SQLD-%EA%B3%B5%EB%B6%80-2</link>
            <guid>https://velog.io/@00kang_jh/SQLD-%EA%B3%B5%EB%B6%80-2</guid>
            <pubDate>Tue, 04 Mar 2025 06:53:08 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-entity-attribute-instance-개념-정리">📌 Entity, Attribute, Instance 개념 정리</h1>
<p>데이터 모델링에서 <strong>Entity(개체), Attribute(속성), Instance(인스턴스)</strong> 개념을 이해하는 것이 중요하다. 이를 쉽게 설명하기 위해 간단한 예제와 함께 살펴보자.  </p>
<hr>
<h2 id="🔹-개념-정리">🔹 개념 정리</h2>
<ul>
<li><strong>Entity (개체):</strong> 관리할 대상이 되는 데이터 (예: 학생, 강의, 도서 등)  </li>
<li><strong>Attribute (속성):</strong> 개체가 가지는 정보 (예: 학번, 이름, 학년 등)  </li>
<li><strong>Instance (인스턴스):</strong> 개체(Entity)의 실제 데이터 값 (예: 학번 <code>2023001</code>, 이름 <code>김철수</code>)  </li>
</ul>
<blockquote>
<p><strong>Entity는 데이터를 저장하는 개체, Attribute는 개체가 가지는 속성, Instance는 실제 데이터를 의미한다.</strong></p>
</blockquote>
<hr>
<h2 id="🎯-이해하기-쉬운-비유">🎯 이해하기 쉬운 비유</h2>
<p>📦 <strong>택배 시스템으로 설명하면?</strong>  </p>
<ul>
<li><strong>Entity (개체) → &quot;택배 상자&quot;</strong>  </li>
<li><strong>Attribute (속성) → &quot;택배 상자에 붙어 있는 송장 (수령인, 주소, 무게)&quot;</strong>  </li>
<li><strong>Instance (인스턴스) → &quot;실제 배송 중인 특정 택배&quot;</strong>  </li>
</ul>
<p>이렇게 비유하면 <strong>Entity, Attribute, Instance</strong> 개념이 더 쉽게 이해될 것이다! 🚀  </p>
<hr>
<h1 id="🔹-entity-엔터티">🔹 Entity (엔터티)</h1>
<blockquote>
<p><strong>&quot;식별 가능한 객체&quot;</strong>  </p>
</blockquote>
<h3 id="✅-엔터티의-특징">✅ 엔터티의 특징</h3>
<ol>
<li><strong>업무에서 사용되는 정보여야 함</strong>  </li>
<li><strong>유일성을 보장하는 식별자(Primary Key)가 있어야 함</strong>  </li>
<li><strong>두 개 이상의 인스턴스(Row)를 가져야 함</strong>  </li>
<li><strong>반드시 속성(Attribute)을 포함해야 함</strong>  </li>
<li><strong>다른 엔터티와 하나 이상의 관계(Relationship)를 가져야 함</strong>  </li>
</ol>
<hr>
<h2 id="🔹-엔터티의-분류">🔹 엔터티의 분류</h2>
<h3 id="1️⃣-유형에-따른-분류">1️⃣ <strong>유형에 따른 분류</strong></h3>
<ul>
<li><strong>유형(Entity):</strong> 물리적인 형태가 존재 (예: <code>학생</code>, <code>도서</code>)  </li>
<li><strong>개념(Entity):</strong> 물리적 형태 없이 개념적으로 존재 (예: <code>강의 계획</code>)  </li>
<li><strong>사건(Entity):</strong> 행위를 통해 발생하며, 빈번하고 통계 자료로 활용 가능 (예: <code>주문 내역</code>)  </li>
</ul>
<h3 id="2️⃣-발생-시점에-따른-분류">2️⃣ <strong>발생 시점에 따른 분류</strong></h3>
<ul>
<li><strong>기본 엔터티:</strong> 원래 존재하는 독립적인 정보, 자식 엔터티를 가질 수 있음  </li>
<li><strong>중심 엔터티:</strong> 기본 엔터티에서 파생되며, 데이터 양이 많고 업무에서 중심적인 역할 수행  </li>
<li><strong>행위 엔터티:</strong> 두 개 이상의 엔터티에서 파생되며, 데이터의 수정과 증가가 잦음  </li>
</ul>
<hr>
<h1 id="🔹-attribute-속성">🔹 Attribute (속성)</h1>
<blockquote>
<p><strong>&quot;객체의 특징&quot;</strong>  </p>
</blockquote>
<p>예를 들어, &quot;사람&quot;이라는 객체가 있다면 <strong>이름, 나이, 생년월일</strong> 등 다양한 특징이 존재할 것이다. 이처럼 개체(Entity)의 특정 정보를 나타내는 요소를 <strong>속성(Attribute)</strong>이라고 한다.  </p>
<hr>
<h2 id="🔹-속성의-분류">🔹 속성의 분류</h2>
<h3 id="1️⃣-특성에-따른-분류">1️⃣ <strong>특성에 따른 분류</strong></h3>
<ul>
<li><strong>기본 속성 (Basic Attribute):</strong> 업무 프로세스에서 바로 정의할 수 있는 속성  </li>
<li><strong>설계 속성 (Designed Attribute):</strong> 업무에 존재하지 않지만 설계 시 필요하다고 판단되는 속성 (예: <code>고유번호</code>)  </li>
<li><strong>파생 속성 (Derived Attribute):</strong> 다른 속성의 값을 계산하거나 특정 규칙으로 변형하여 생성하는 속성  </li>
</ul>
<h3 id="2️⃣-구성-방식에-따른-분류">2️⃣ <strong>구성 방식에 따른 분류</strong></h3>
<ul>
<li><strong>PK (Primary Key) 속성:</strong> 엔터티의 인스턴스를 식별할 수 있는 속성  </li>
<li><strong>FK (Foreign Key) 속성:</strong> 다른 엔터티의 속성에서 가져온 속성  </li>
<li><strong>일반 속성:</strong> PK, FK를 제외한 나머지 속성  </li>
</ul>
<hr>
<h2 id="🔹-도메인-domain">🔹 도메인 (Domain)</h2>
<blockquote>
<p><strong>&quot;속성이 가질 수 있는 값의 범위&quot;</strong>  </p>
</blockquote>
<p>예를 들어, <code>성별</code> 속성의 도메인은 <strong>[남, 여]</strong> 와 같이 미리 정의된 값의 범위를 가진다.  </p>
<hr>
<p>📖 <strong>참고 도서:</strong> 《SQL개발자 SQLD 과외노트》 - 정미나, 시대에듀  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQLD 공부 (1)]]></title>
            <link>https://velog.io/@00kang_jh/SQLD-%EA%B3%B5%EB%B6%80-1</link>
            <guid>https://velog.io/@00kang_jh/SQLD-%EA%B3%B5%EB%B6%80-1</guid>
            <pubDate>Sat, 01 Mar 2025 02:02:23 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-데이터-모델의-이해">📌 데이터 모델의 이해</h1>
<h2 id="🔹-모델링modeling이란">🔹 모델링(Modeling)이란?</h2>
<blockquote>
<p><strong>&quot;현실 세계를 단순화하여 표현하는 기법&quot;</strong></p>
</blockquote>
<h3 id="✅-모델링의-3가지-조건">✅ 모델링의 3가지 조건</h3>
<ol>
<li><strong>현실 세계를 반영해야 함</strong>  </li>
<li><strong>단순화하여 표현해야 함</strong>  </li>
<li><strong>관찰하고자 하는 데이터를 모델로 설계해야 함</strong>  </li>
</ol>
<hr>
<h2 id="🔹-모델링의-3가지-특징">🔹 모델링의 3가지 특징</h2>
<h3 id="1️⃣-추상화abstraction">1️⃣ <strong>추상화(Abstraction)</strong></h3>
<ul>
<li>현실 세계를 일정한 형식으로 표현하는 과정  </li>
<li>아이디어나 개념을 간략하게 정리  </li>
</ul>
<h3 id="2️⃣-단순화simplification">2️⃣ <strong>단순화(Simplification)</strong></h3>
<ul>
<li>복잡한 현실 세계를 정해진 표기법으로 쉽게 표현  </li>
</ul>
<h3 id="3️⃣-명확화clarity">3️⃣ <strong>명확화(Clarity)</strong></h3>
<ul>
<li>불분명한 요소를 제거하여 해석이 명확하게 가능하도록 표현  </li>
</ul>
<hr>
<h2 id="🔹-모델링의-3가지-관점">🔹 모델링의 3가지 관점</h2>
<h3 id="1️⃣-데이터-관점-data-perspective">1️⃣ <strong>데이터 관점 (Data Perspective)</strong></h3>
<ul>
<li><strong>데이터 중심</strong>의 모델링  </li>
<li>업무에 연관된 데이터와 <strong>데이터 간의 관계</strong>를 분석  </li>
</ul>
<h3 id="2️⃣-프로세스-관점-process-perspective">2️⃣ <strong>프로세스 관점 (Process Perspective)</strong></h3>
<ul>
<li><strong>업무 프로세스 중심</strong>의 모델링  </li>
<li>현재 또는 미래의 업무 처리 방식에 초점  </li>
</ul>
<h3 id="3️⃣-데이터와-프로세스의-상관-관점-interaction-perspective">3️⃣ <strong>데이터와 프로세스의 상관 관점 (Interaction Perspective)</strong></h3>
<ul>
<li><strong>데이터와 프로세스의 관계</strong>를 분석  </li>
<li>프로세스의 흐름에 따라 데이터가 어떻게 변하는지 모델링  </li>
</ul>
<hr>
<h2 id="🔹-모델링의-3가지-단계">🔹 모델링의 3가지 단계</h2>
<h3 id="1️⃣-개념적-데이터-모델링-conceptual-data-modeling">1️⃣ 개념적 데이터 모델링 (Conceptual Data Modeling)</h3>
<ul>
<li><strong>가장 추상화 수준이 높은 모델링 단계</strong>  </li>
<li>기업(전사) 차원의 데이터를 분석하고, <strong>업무 중심적이고 포괄적인 모델링</strong>을 수행  </li>
<li>개체(Entity)와 그 관계(Relationship)를 정의하는 단계  </li>
</ul>
<h3 id="2️⃣-논리적-데이터-모델링-logical-data-modeling">2️⃣ 논리적 데이터 모델링 (Logical Data Modeling)</h3>
<ul>
<li><strong>데이터베이스를 설계하기 위한 핵심 모델링 단계</strong>  </li>
<li><strong>재사용성이 가장 높은 모델링</strong>으로,  <ul>
<li>Key(키), 속성(Attribute), 관계(Relationship) 등을 상세하게 정의  </li>
</ul>
</li>
<li>DBMS에 <strong>독립적인</strong> 설계가 가능  </li>
</ul>
<h3 id="3️⃣-물리적-데이터-모델링-physical-data-modeling">3️⃣ 물리적 데이터 모델링 (Physical Data Modeling)</h3>
<ul>
<li><strong>실제 데이터베이스로 구현할 수 있도록 성능과 가용성을 고려하는 단계</strong>  </li>
<li>데이터의 <strong>물리적 저장 구조(테이블, 컬럼, 인덱스 등)</strong>를 정의  </li>
<li>DBMS의 특성을 반영하여 최적화 진행  </li>
</ul>
<hr>
<h2 id="🔹-데이터의-독립성-data-independence">🔹 데이터의 독립성 (Data Independence)</h2>
<blockquote>
<p><strong>&quot;ANSI-SPARC 아키텍처는 DBMS의 추상적인 설계 표준이다.&quot;</strong><br>✅ <strong>총 3단계의 스키마 구조를 갖고 있음</strong>  </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/17e29daf-7924-4eb2-b65c-6a5ee2f38b13/image.png" alt="ANSI-SPARC 아키텍처"></p>
<h3 id="1️⃣-외부-스키마-external-schema">1️⃣ 외부 스키마 (External Schema)</h3>
<ul>
<li><strong>사용자 관점 (User View)</strong>  </li>
<li>사용자가 데이터베이스를 보는 관점에 따라 <strong>각기 다른 스키마</strong>를 정의  </li>
<li>여러 개의 외부 스키마가 존재할 수 있음 (Multiple User&#39;s View)  </li>
</ul>
<h3 id="2️⃣-개념-스키마-conceptual-schema">2️⃣ 개념 스키마 (Conceptual Schema)</h3>
<ul>
<li><strong>통합된 관점 (Community View of DB)</strong>  </li>
<li>데이터베이스 전체 구조를 나타내는 스키마  </li>
<li><strong>모든 사용자</strong>가 공유하는 <strong>데이터의 구조 및 관계</strong>를 정의  </li>
</ul>
<h3 id="3️⃣-내부-스키마-internal-schema">3️⃣ 내부 스키마 (Internal Schema)</h3>
<ul>
<li><strong>물리적인 관점 (Physical Representation)</strong>  </li>
<li>데이터의 <strong>저장 방식, 인덱스, 컬럼 정의</strong> 등을 포함  </li>
<li>실제 물리적인 저장 구조를 나타냄  </li>
</ul>
<h3 id="🔸-데이터-독립성이-보장하는-2가지-원칙">🔸 데이터 독립성이 보장하는 2가지 원칙</h3>
<ol>
<li><strong>논리적 독립성 (Logical Independence)</strong>  <ul>
<li><strong>개념 스키마</strong>가 변경되어도 <strong>외부 스키마(사용자 관점)는 영향을 받지 않음</strong>  </li>
</ul>
</li>
<li><strong>물리적 독립성 (Physical Independence)</strong>  <ul>
<li><strong>내부 스키마</strong>(저장 구조)가 변경되어도 <strong>외부/개념 스키마는 영향을 받지 않음</strong>  </li>
</ul>
</li>
</ol>
<hr>
<h2 id="🔹-erd-entity-relationship-diagram">🔹 ERD (Entity Relationship Diagram)</h2>
<blockquote>
<p><strong>&quot;어떤 개체(Entity)들이 존재하고, 그들 간에 어떤 관계(Relationship)가 있는지를 시각적으로 표현한 다이어그램&quot;</strong></p>
</blockquote>
<p>✅ <strong>ERD의 주요 구성 요소</strong></p>
<ul>
<li><strong>Entity(개체):</strong> 데이터를 저장하는 주요 단위 (예: 고객, 주문, 제품)  </li>
<li><strong>Attribute(속성):</strong> 개체가 가지는 정보 (예: 고객 이름, 주문 날짜)  </li>
<li><strong>Relationship(관계):</strong> 개체 간의 연관성 (예: 고객은 주문을 한다)  </li>
</ul>
<p>📝 <strong>이 단계에서는 개념을 큰 틀에서 이해하는 것이 중요함!</strong>  </p>
<hr>
<p>📖 <strong>참고 도서:</strong> 《SQL개발자 SQLD 과외노트》 - 정미나, 시대에듀 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CodingTest_Day1_Array]]></title>
            <link>https://velog.io/@00kang_jh/CodingTestDay1Array</link>
            <guid>https://velog.io/@00kang_jh/CodingTestDay1Array</guid>
            <pubDate>Thu, 02 Jan 2025 14:12:23 GMT</pubDate>
            <description><![CDATA[<p>이번 주제는 Array 배열입니다. 우리는 이미 잘 알고 있기에 복습한다고 생각하시면 좋을 것 같습니다.</p>
<hr>
<h2 id="array배열의-기본-개념">Array(배열)의 기본 개념</h2>
<h4 id="배열이란">배열이란?</h4>
<p><strong>&quot;동일한 타입의 데이터를 연속된 메모리 공간에 저장하는 자료구조&quot;</strong> 라고 정의할 수 있습니다.</p>
<h4 id="배열의-장점과-단점">배열의 장점과 단점</h4>
<p>장점 : 빠른 접근 속도 (<strong>O(1)</strong>로 인덱스 접근이 가능합니다.)
단점 : 크기가 고정되어 있으며, 삽입 or 삭제 시 성능이 저하됩니다.</p>
<hr>
<h2 id="array배열의-연산">Array(배열)의 연산</h2>
<h4 id="배열-생성-및-초기화">배열 생성 및 초기화</h4>
<pre><code class="language-java">int[] arr = new int[5]; // 크기가 5인 배열 생성
int[] arr2 = {1, 2, 3, 4, 5}; // 초기화된 배열</code></pre>
<h4 id="요소-접근-및-수정">요소 접근 및 수정</h4>
<pre><code class="language-java">arr[0] = 10; // 배열 첫 번째 요소를 10으로 수정
System.out.println(arr[0]); // 출력: 10</code></pre>
<h4 id="다차원-배열">다차원 배열</h4>
<pre><code class="language-java">int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
System.out.println(matrix[0][1]); // 출력: 2</code></pre>
<h4 id="가변-배열-jagged-array">가변 배열 (Jagged Array)</h4>
<pre><code class="language-java">int[][] jagged = new int[3][];
jagged[0] = new int[2]; // 첫 번째 행의 길이: 2
jagged[1] = new int[3]; // 두 번째 행의 길이: 3
jagged[2] = new int[1]; // 세 번째 행의 길이: 1</code></pre>
<hr>
<h2 id="array배열의-알고리즘적-활용">Array(배열)의 알고리즘적 활용</h2>
<h4 id="정렬오름차순내림차순">정렬(오름차순/내림차순)</h4>
<pre><code class="language-java">Arrays.sort(arr); // 오름차순 정렬
Arrays.sort(arr, Collections.reverseOrder()); // 내림차순 정렬 (Integer[] 필요)</code></pre>
<h4 id="탐색linear-search-binary-search-최대값최소값-찾기">탐색(Linear Search, Binary Search, 최대값/최소값 찾기)</h4>
<pre><code class="language-java">//선형 탐색(Linear Search)
int key = 3;
for (int i = 0; i &lt; arr.length; i++) {
    if (arr[i] == key) {
        System.out.println(&quot;Found at index: &quot; + i);
    }
}</code></pre>
<pre><code class="language-java">//이진 탐색 (Binary Search)
int index = Arrays.binarySearch(arr, key); // 정렬된 배열에 사용
System.out.println(&quot;Index: &quot; + index);
</code></pre>
<pre><code class="language-java">//최대값 찾기, 최소값은 if문만 변경해서 작성하면 됨.
int max = arr[0];
for (int i = 1; i &lt; arr.length; i++) {
    if (arr[i] &gt; max) max = arr[i];
}
System.out.println(&quot;Max: &quot; + max);
</code></pre>
<hr>
<h2 id="array와-arraylist의-차이">Array와 ArrayList의 차이</h2>
<ul>
<li>배열은 크기가 고정되지만, ArrayList는 동적으로 크기가 변경됩니다.</li>
<li>배열은 기본 자료형을 저장할 수 있지만, ArrayList는 객체만 저장 가능합니다.</li>
</ul>
<hr>
<p>빠르고 간단하게 Array에 대해서 review 해봤습니다. 오늘도 error / issue 없는 하루 되시길 바랍니다. 감사합니다 :) </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Coding Test 공부 개요]]></title>
            <link>https://velog.io/@00kang_jh/Coding-Test-%EA%B3%B5%EB%B6%80-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@00kang_jh/Coding-Test-%EA%B3%B5%EB%B6%80-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Wed, 01 Jan 2025 06:01:44 GMT</pubDate>
            <description><![CDATA[<p>4년의 학부생활은 마쳤고 취업을 위해서 할 수 있는 것들 중 하나인 coding Test 준비를 해보려고 합니다. coding Test는 어쩌면 가장 개발자다운 면접이라고 생각합니다. 또 가장 흔하게도 보이는 방식이면서도 가장 어려운 방식이기도 합니다. 무엇이든 짧은 시간에 이루어내는 것이 어렵습니다. 하지만 이 어려운 걸 이번 기회에 해보려고 합니다. 뭐든 노력해서 안되는 것은 없다. </p>
<ol>
<li>자료구조 공부 및 문제 풀이</li>
<li>빅오 표기법 (big-O nation) 익히기</li>
<li>알고리즘 공부 및 문제 풀이</li>
<li>알고리즘 분석</li>
</ol>
<p>다음과 같은 순서로 진행하려고 합니다.</p>
<hr>
<h3 id="1-자료구조data-structure-공부-및-문제-풀이">1. 자료구조(Data structure) 공부 및 문제 풀이</h3>
<p>앞으로 공부할 자료구조는 <strong>Array, String, Stack, Queue, Priority Queue, Deque, Hash</strong> 정도 입니다. 자바로 coding test를 준비할 예정이기 때문에 *<em>이론 공부 -&gt; 자바 내장 클래스의 메소드 공부 -&gt; 백준 문제 풀이 *</em> 순서로 진행할 예정입니다. </p>
<h4 id="--이론-공부-간단-요약">- 이론 공부 간단 요약</h4>
<pre><code>Array: 배열의 구조와 특징, 탐색 및 정렬 알고리즘
String: 문자열의 처리 방법, 다양한 문자열 메소드
Stack: LIFO 구조, 다양한 활용 예시
Queue: FIFO 구조, 일반 큐와 원형 큐의 차이점
Priority Queue: 우선순위 큐의 개념과 활용
Deque: 양쪽 끝에서 삽입과 삭제가 가능한 자료구조
Hash: 해시 테이블의 원리, 충돌 해결 방식</code></pre><h4 id="--자바-내장-클래스--간단-요약">- 자바 내장 클래스  간단 요약</h4>
<pre><code>Array: Arrays 클래스의 메소드 (정렬, 검색 등)
String: String 클래스의 주요 메소드
Stack: Stack 클래스 (push, pop, peek 등)
Queue: Queue 인터페이스와 LinkedList 또는 ArrayDeque 구현
Priority Queue: PriorityQueue 클래스
Deque: Deque 인터페이스와 ArrayDeque 클래스
Hash: HashMap, HashSet 클래스의 메소드</code></pre><p>백준 문제 풀이의 경우에는 하루 평균 3~5문제 풀 예정입니다.</p>
<hr>
<h3 id="2-빅오-표기법-big-o-nation-익히기">2. 빅오 표기법 (big-O nation) 익히기</h3>
<p>알고리즘 공부하기 앞서서 빅오 표기법을 공부하려고 합니다. 해당 표기법은 알고리즘에서 가장 기초가 되는 부분으로 알고리즘의 시간 복잡도, 공간 복잡도를 나타낼 수 있는 수학적 기법입니다. 공부해야하는 이유는 효율성 평가, 최적화, 문제 해결, 면접 준비에 필요하고 필수적이기 때문입니다.</p>
<pre><code>효율성 평가: 알고리즘의 성능을 평가하고 다른 알고리즘과 비교할 수 있습니다.
최적화: 성능을 개선하기 위해 어떤 부분을 최적화해야 하는지 이해할 수 있습니다.
문제 해결: 특정 문제에 적합한 알고리즘을 선택하는 데 도움을 줍니다.
면접 준비: 많은 코딩 테스트 인터뷰에서 알고리즘의 복잡도를 물어보므로 필수적인 지식입니다.</code></pre><p>기본 개념 정리 후 실제 코드를 보면서 시간 복잡도, 공간 복잡도를 구해보면서 공부할 예정입니다.</p>
<hr>
<h3 id="3-알고리즘-공부-및-문제-풀이">3. 알고리즘 공부 및 문제 풀이</h3>
<p>앞으로 공부할 알고리즘에 종류에서 정<strong>렬(quick sort, merge sort), 탐색(binary search, DFS, BFS), 재귀, DP, Graph</strong>를 공부할 예정입니다. 해당 공부는 <strong>이론 공부 -&gt; 백준 문제(실버) -&gt; 백준 문제(골드)</strong> 순서로 진행할 예정입니다. 하루 평균 3~5문제정도 풀이할 예정입니다.</p>
<h4 id="--이론-공부-간단-요약-1">- 이론 공부 간단 요약</h4>
<ol>
        <li>
            <strong>정렬 알고리즘</strong>
            <ul>
                <li>퀵 정렬: 평균 O(n log n), 분할 정복 방식.</li>
                <li>병합 정렬: O(n log n), 안정적인 정렬.</li>
            </ul>
        </li>
        <li>
            <strong>탐색 알고리즘</strong>
            <ul>
                <li>이진 탐색: O(log n), 정렬된 배열에서 특정 원소 찾기.</li>
                <li>DFS (깊이 우선 탐색): 재귀적 또는 스택을 이용한 탐색.</li>
                <li>BFS (너비 우선 탐색): 큐를 이용하여 레벨별 탐색.</li>
            </ul>
        </li>
        <li>
            <strong>재귀</strong>
            <ul>
                <li>자기 자신을 호출하여 문제를 해결하는 기법.</li>
                <li>기본 구조와 종료 조건 이해.</li>
            </ul>
        </li>
        <li>
            <strong>동적 프로그래밍 (DP)</strong>
            <ul>
                <li>문제를 더 작은 하위 문제로 나누어 해결.</li>
                <li>메모이제이션 또는 타뷸레이션 기법 사용.</li>
            </ul>
        </li>
        <li>
            <strong>그래프</strong>
            <ul>
                <li>그래프 표현 방법 (인접 행렬, 인접 리스트).</li>
                <li>그래프 탐색 (DFS, BFS)과 최단 경로 알고리즘 이해.</li>
            </ul>
        </li>
    </ol>

<hr>
<h3 id="4-알고리즘-분석">4. 알고리즘 분석</h3>
<p>알고리즘 분석은 주어진 문제를 해결하기 위해 작성한 코드의 성능을 평가하는 과정입니다. 이를 통해 알고리즘의 효율성을 이해하고, 최적화할 수 있는 기회를 제공합니다. 앞으로의 알고리즘 분석은 다음과 같은 방법으로 진행할 예정입니다:</p>
<ol>
    <li>
        <strong>시간 복잡도 계산</strong>
        <ul>
            <li>코드의 실행 시간이 입력 크기에 따라 어떻게 변화하는지를 분석합니다.</li>
            <li>반복문이나 재귀 호출의 횟수를 세고, Big O 표기법으로 표현합니다.</li>
            <li>예를 들어, 중첩 반복문이 있는 경우 O(n<sup>2</sup>)로 표현할 수 있습니다.</li>
        </ul>
    </li>
    <li>
        <strong>공간 복잡도 계산</strong>
        <ul>
            <li>코드가 사용하는 메모리의 양을 분석합니다.</li>
            <li>변수, 배열, 객체 등에서 사용하는 메모리의 양을 고려합니다.</li>
            <li>재귀 호출로 인해 사용하는 스택 메모리를 포함하여 평가합니다.</li>
        </ul>
    </li>
    <li>
        <strong>다른 사람의 코드 분석</strong>
        <ul>
            <li>다른 사람의 알고리즘을 분석하여 다양한 접근 방식을 배웁니다.</li>
            <li>코드의 시간 복잡도와 공간 복잡도를 이해합니다.</li>
            <li>자신의 코드와 비교하여 개선할 수 있는 부분을 찾아봅니다.</li>
        </ul>
    </li>
</ol>

<p>이러한 단계들을 통해 알고리즘 분석의 기초를 다지고, 문제 해결 능력을 향상시키는 데 중점을 둘 것입니다. 알고리즘의 성능을 체계적으로 평가함으로써, 더 효율적인 코드 작성과 문제 해결 전략을 개발할 수 있도록 노력하겠습니다.</p>
<hr>
<p>happy new year :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩 테스트 필수 문법 (2)]]></title>
            <link>https://velog.io/@00kang_jh/%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%84%EC%88%98-%EB%AC%B8%EB%B2%95-2</link>
            <guid>https://velog.io/@00kang_jh/%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%84%EC%88%98-%EB%AC%B8%EB%B2%95-2</guid>
            <pubDate>Thu, 14 Nov 2024 01:54:05 GMT</pubDate>
            <description><![CDATA[<h3 id="컬렉션-프레임-워크collection-framework">컬렉션 프레임 워크(Collection Framework)</h3>
<hr>
<p>여러 개의 값을 저장하고 그 값을 쉽고, 효율적으로 처리해주는 표준화 클래스의 집합 입니다. 쉽게 말하자면 Data structure인 리스트, 큐, 스택, 해시맵 등을 직접 구현하지 않고 쉽게 사용할 수 있습니다. 대표적으로 <code>ArrayList</code> , <code>Stack</code>, <code>Queue</code>, <code>ArrayDeque</code>, <code>HashMap</code> 등이 있습니다.</p>
<p><strong>컬렉션 프레임워크의 주요 클래스</strong></p>
<ul>
<li><strong>ArrayList</strong>
ArrayList는 가변 크기를 가지며, 요소를 인덱스에 따라 저장하는 리스트입니다.<pre><code class="language-java">import java.util.ArrayList;
</code></pre>
</li>
</ul>
<p>public class ArrayListExample {
    public static void main(String[] args) {
        // Create
        ArrayList<String> list = new ArrayList&lt;&gt;();
        list.add(&quot;Apple&quot;);
        list.add(&quot;Banana&quot;);</p>
<pre><code>    // Read
    System.out.println(list.get(0)); // Apple

    // Update
    list.set(1, &quot;Cherry&quot;);
    System.out.println(list.get(1)); // Cherry

    // Delete
    list.remove(0);
    System.out.println(list); // [Cherry]
}</code></pre><p>}</p>
<pre><code>- **Stack**
Stack은 `LIFO(Last In First Out)` 구조로, 요소를 쌓아 올리는 형식입니다.

```java
import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        // Create
        Stack&lt;String&gt; stack = new Stack&lt;&gt;();
        stack.push(&quot;A&quot;);
        stack.push(&quot;B&quot;);

        // Read
        System.out.println(stack.peek()); // B

        // Update (Stack에서는 update가 아닌 pop 후 push로 처리)
        stack.pop(); // 제거
        stack.push(&quot;C&quot;); // 새 요소 추가

        // Delete
        stack.pop(); // C 제거
        System.out.println(stack); // [A]
    }
}</code></pre><ul>
<li><strong>Queue</strong>
Queue는 <code>FIFO(First In First Out)</code> 구조로, 먼저 들어온 요소가 먼저 나갑니다.</li>
</ul>
<pre><code class="language-java">import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // Create
        Queue&lt;String&gt; queue = new LinkedList&lt;&gt;();
        queue.add(&quot;1&quot;);
        queue.add(&quot;2&quot;);

        // Read
        System.out.println(queue.peek()); // 1

        // Update (Queue는 직접적으로 업데이트 불가, 제거 후 추가)
        queue.poll(); // 1 제거
        queue.add(&quot;3&quot;); // 새 요소 추가

        // Delete
        queue.poll(); // 2 제거
        System.out.println(queue); // [3]
    }
}</code></pre>
<ul>
<li><strong>ArrayDeque</strong>
ArrayDeque는 덱(Double Ended Queue)으로, 양쪽 끝에서 요소를 추가하거나 제거할 수 있습니다.</li>
</ul>
<pre><code class="language-java">import java.util.ArrayDeque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        // Create
        ArrayDeque&lt;String&gt; deque = new ArrayDeque&lt;&gt;();
        deque.add(&quot;X&quot;);
        deque.add(&quot;Y&quot;);

        // Read
        System.out.println(deque.peekFirst()); // X

        // Update (Update가 아닌 제거 후 추가)
        deque.pollFirst(); // X 제거
        deque.addFirst(&quot;Z&quot;); // 새 요소 추가

        // Delete
        deque.pollLast(); // Y 제거
        System.out.println(deque); // [Z]
    }
}</code></pre>
<ul>
<li><strong>HashMap</strong>
HashMap은 키-값 쌍으로 데이터를 저장하며, 키를 통해 값을 빠르게 조회할 수 있습니다.</li>
</ul>
<pre><code class="language-java">import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // Create
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;Apple&quot;, 1);
        map.put(&quot;Banana&quot;, 2);

        // Read
        System.out.println(map.get(&quot;Apple&quot;)); // 1

        // Update
        map.put(&quot;Apple&quot;, 3); // 값 업데이트
        System.out.println(map.get(&quot;Apple&quot;)); // 3

        // Delete
        map.remove(&quot;Banana&quot;);
        System.out.println(map); // {Apple=3}
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩 테스트 필수 문법 (1)]]></title>
            <link>https://velog.io/@00kang_jh/%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%84%EC%88%98-%EB%AC%B8%EB%B2%951</link>
            <guid>https://velog.io/@00kang_jh/%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%ED%95%84%EC%88%98-%EB%AC%B8%EB%B2%951</guid>
            <pubDate>Thu, 14 Nov 2024 01:31:24 GMT</pubDate>
            <description><![CDATA[<h3 id="프리미티브-타입과-레퍼런스-타입">프리미티브 타입과 레퍼런스 타입</h3>
<hr>
<p><strong>프리미티브 타입(Primitive type)</strong>
원시 타입 은 정수, 실수, 문자, 논리 리터럴 등의 실제 데이터 값을 저장하는 타입입니다.</p>
<p><code>int</code>, <code>long</code>, <code>double</code>, <code>float</code>, <code>boolean</code>, <code>byte</code>, <code>short</code>, <code>char</code></p>
<p><strong>레퍼런스 타입(Reference Type)</strong>
참조 타입 은 객체(Object)를 참조(주소를 저장) 하는 타입으로 메모리 번지 값을 통해 객체를 참조하는 타입이다.</p>
<p><code>Integer</code>, <code>Long</code>, <code>Double</code>, <code>Float</code>, <code>Boolean</code>, <code>Byte</code>, <code>Short</code>, <code>Char</code></p>
<blockquote>
<p>연산 속도는 Primitive type가 Reference Type보다 빠릅니다.</p>
</blockquote>
<ul>
<li><strong>비교 연산</strong><pre><code class="language-java">System.out.println(a==b); // 같은 값인지 비교, false
System.out.println(a!=b); // 같지 않은 값인지 비교, false
System.out.println(a&gt;b);  // 왼쪽 같이 더 큰지 비교, false
System.out.println(a&lt;b);  // 왼쪽 값이 더 작은지 비교, false
System.out.println(a&gt;=b); // 왼쪽 값이 더 크거나 같은지 비교, false
System.out.println(a&lt;=b); // 왼쪽 값이 더 작거나 같은지 비교, false</code></pre>
</li>
<li><strong>비트 연산</strong><pre><code class="language-java">int a = 13;
int b = 4;
System.out.println(a &amp; b); // AND, 4
System.out.println(a | b); // OR, 13
System.out.println(a ^ b); // XOR, 9
System.out.println(~ a); // NOT, -14
System.out.println(a &lt;&lt; 2); // 왼쪽 시프트 (a에 2^2를 곱한 것과 동일) / 52
System.out.println(a &gt;&gt; 1); // 오른쪽 시프트 (a에 2^1로 나눈 것과 동일) / 6</code></pre>
</li>
</ul>
<p>부동소수형 데이터는 표현 과정에서 오차가 발생할 수 있다. 이것을 <code>엡실론(epsilon)</code>이라고 하며 항상 생각해야합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenCV와 C++ 설치하기: Apple Silicon M2에서 VS Code 사용하기 🖥️]]></title>
            <link>https://velog.io/@00kang_jh/Computer-Vision-0</link>
            <guid>https://velog.io/@00kang_jh/Computer-Vision-0</guid>
            <pubDate>Mon, 14 Oct 2024 02:23:03 GMT</pubDate>
            <description><![CDATA[<p>OpenCV는 컴퓨터 비전 분야에서 널리 사용되는 라이브러리로, 이미지와 비디오 처리에 강력한 기능을 제공합니다. C++로 OpenCV를 사용하면 성능을 극대화할 수 있습니다. 이번 포스팅에서는 Apple Silicon M2에서 VS Code를 사용하여 OpenCV와 C++ 환경을 설정하는 방법을 자세히 알아보겠습니다.</p>
<h3 id="1개발환경-안내">1.개발환경 안내</h3>
<p>본인은 MacBook Air, Apple M2, macOS Sonoma 14.6.2 입니다.</p>
<h3 id="2-opencv와-c-소개">2. OpenCV와 C++ 소개</h3>
<p>OpenCV는 다양한 이미지 처리 기능을 제공하는 오픈 소스 라이브러리입니다. C++는 OpenCV와 함께 사용할 때 성능이 뛰어나기 때문에 많은 개발자들이 선호합니다. OpenCV를 통해 이미지 필터링, 객체 인식, 얼굴 인식 등 다양한 작업을 수행할 수 있습니다.</p>
<h3 id="3-apple-silicon-m2에서의-환경-설정">3. Apple Silicon M2에서의 환경 설정</h3>
<p>Apple Silicon M2는 ARM 아키텍처를 기반으로 하여, 기존의 Intel 기반 Mac과는 다른 환경을 제공합니다. 따라서, M2에서 OpenCV와 C++를 설치하기 위해서는 몇 가지 추가적인 설정이 필요합니다.</p>
<h3 id="4-vs-code-설치-및-설정">4. VS Code 설치 및 설정</h3>
<p>먼저, Visual Studio Code(VS Code)를 설치해야 합니다. VS Code는 다양한 언어를 지원하는 강력한 코드 편집기입니다. Apple Silicon에 맞는 버전을 다운로드하려면 VS Code 공식 웹사이트로 가서 Apple Silicon 버전을 선택하여 설치합니다.</p>
<p>설치가 완료되면, VS Code를 실행하고 C++ 확장 프로그램을 설치합니다. 이를 통해 C++ 개발 환경을 더욱 편리하게 사용할 수 있습니다.</p>
<h3 id="5-homebrew-설치-및-opencv-설치">5. Homebrew 설치 및 OpenCV 설치</h3>
<p>Homebrew는 macOS에서 패키지를 관리하는 도구입니다. Homebrew를 설치하려면 터미널을 열고 아래의 명령어를 입력합니다:</p>
<blockquote>
<p>bash
/bin/bash -c &quot;$(curl -fsSL <a href="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;">https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;</a></p>
</blockquote>
<p>설치가 완료되면, OpenCV를 설치할 수 있습니다. 터미널에 다음 명령어를 입력하여 OpenCV를 설치합니다:</p>
<blockquote>
<p>bash
brew install opencv</p>
</blockquote>
<p>설치가 완료되면, OpenCV의 경로를 확인하고 환경 변수를 설정해야 합니다. 아래의 명령어를 입력하여 OpenCV의 설치 경로를 확인합니다:</p>
<blockquote>
<p>bash
brew info opencv</p>
</blockquote>
<p>이제 VS Code에서 OpenCV를 사용할 수 있도록 설정합니다. VS Code의 설정 파일에 OpenCV의 경로를 추가합니다.</p>
<h3 id="6-opencv와-c-프로젝트-설정">6. OpenCV와 C++ 프로젝트 설정</h3>
<p>이제 OpenCV와 C++ 프로젝트를 설정할 차례입니다. VS Code에서 새로운 C++ 프로젝트를 생성하고, CMakeLists.txt 파일을 생성하여 OpenCV 라이브러리를 포함시킵니다. 아래는 기본적인 CMakeLists.txt의 예시입니다:</p>
<blockquote>
<p>cmake
cmake_minimum_required(VERSION 3.10)
project(<span style="color:blue">프로젝트명</span>) <br> 
find_package(OpenCV REQUIRED)
add_executable(<span style="color:blue">프로젝트명</span>   <span style="color:green">파일명</span>)
target_link_libraries (<span style="color:blue">프로젝트명</span> ${OpenCV_LIBS}) </p>
</blockquote>
<p>이 코드는 지정한 이미지를 불러와서 화면에 표시하는 간단한 예제입니다. 이미지를 불러올 수 없는 경우 오류 메시지를 출력합니다.</p>
<h3 id="7-프로젝트-실행">7. 프로젝트 실행</h3>
<p>CMakeLists.txt를 완성한 후, 우리가 원하는 프로젝트를 실행할 차례입니다. VScode 터미널에서 실행하게 됩니다. 다음 명령어들을 입력하면 됩니다.</p>
<blockquote>
<p>cd build
make
./<span style="color:blue">프로젝트명</span></p>
</blockquote>
<p>다음과 같이 입력하시게 되면 CMakeLists.txt에서 설정한 파일이 실행됨을 알 수 있습니다. 실행한 프로젝트를 종료하시려면 <code>control + z</code> 누르시면 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 멀쩡한 사각형 Java]]></title>
            <link>https://velog.io/@00kang_jh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A9%80%EC%A9%A1%ED%95%9C-%EC%82%AC%EA%B0%81%ED%98%95-Java</link>
            <guid>https://velog.io/@00kang_jh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%A9%80%EC%A9%A1%ED%95%9C-%EC%82%AC%EA%B0%81%ED%98%95-Java</guid>
            <pubDate>Tue, 08 Oct 2024 07:05:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 문제는 프로그래머스 Level 2 [멀쩡한 사각형] (<a href="https://school.programmers.co.kr/learn/courses/30/lessons/62048">https://school.programmers.co.kr/learn/courses/30/lessons/62048</a>) 입니다.</p>
</blockquote>
<hr>
<h3 id="문제-설명">문제 설명</h3>
<p>가로 길이가 <strong>W</strong>cm, 세로 길이가 <strong>H</strong>cm인 직사각형 종이가 있습니다. 종이에는 가로, 세로 방향과 평행하게 격자 형태로 선이 그어져 있으며, 모든 격자칸은 1cm x 1cm 크기입니다. 이 종이를 격자 선을 따라 1cm × 1cm의 정사각형으로 잘라 사용할 예정이었는데, 누군가가 <strong>이 종이를 대각선 꼭지점 2개를 잇는 방향으로 잘라 놓았습니다.</strong> 그러므로 현재 직사각형 종이는 크기가 같은 직각삼각형 2개로 나누어진 상태입니다. 새로운 종이를 구할 수 없는 상태이기 때문에, 이 종이에서 원래 종이의 가로, 세로 방향과 평행하게 1cm × 1cm로 잘라 사용할 수 있는 만큼만 사용하기로 하였습니다.</p>
<p>가로의 길이 W와 세로의 길이 H가 주어질 때, 사용할 수 있는 정사각형의 개수를 구하는 solution 함수를 완성해 주세요.</p>
<blockquote>
<p><strong>한줄로 요약하면!!</strong>
가로/세로 값이 해당 사이즈의 직사각형을 반절로 접었을 때, 그 대각선을 지나는 칸을 제외한 나머지 칸을 구하면 됩니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/a9d1689b-8cdb-4c61-b7e0-02bdc85818bf/image.png" alt=""></p>
<hr>
<h3 id="제한사항">제한사항</h3>
<p>W, H : 1억 이하의 자연수</p>
<hr>
<h3 id="입출력-예시">입출력 예시</h3>
<p>w = 8 , h = 12 일때, 전체 칸은 96칸 입니다.
그 직사각형의 대각선을 그립니다. 해당 대각선이 지나는 칸이 총 16칸이기 때문에 그 만큼을 제외한 80칸이 return 됩니다. </p>
<hr>
<h3 id="코드-작성-전-생각-정리">코드 작성 전, 생각 정리</h3>
<ul>
<li>파라미터가 int 타입인데, return 타입이 long이라 계산할 때 타입 변환하기</li>
<li>다른 값들 통해서 규칙성을 찾기<ul>
<li>위의 사진을 보면, 총 4번의 동일한 패턴을 보인다. </li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>여기서 4라는 숫자가 어떻게 나올 수 있을까?</strong>
예제에서 8과 12일 때, 4라는 숫자는 여러가지로 생각해볼 수 있지만 최대공약수로 추측할 수 있다. 그 외의 경우에도 고려해보면 공약수가 없는 경우를 제외하면 모든 경우에 최대공약수로 결과값을 구하면 원하는 값과 동일하다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/eb912aaf-f1a1-4bc7-823a-27dfd5215f07/image.png" alt=""></p>
<ol>
<li><p>(2,3)일 때, 4개의 직사각형이 영향을 받음</p>
</li>
<li><p>(5,2)일 때, 6개의 직사각형이 영향을 받음</p>
<p>일반식으로 만들어보면!!
영향을 받는 직사각형의 수 = (너비) + (높이) - 1 이 되고,
결론적으로 전체 계산식을 작성하면
<strong>(너비) x (높이) - ((너비) + (높이) - 1) x (최대공약수)</strong>이 됩니다.</p>
</li>
</ol>
<hr>
<h3 id="코드자바">코드(자바)</h3>
<pre><code class="language-java">class Solution {
    public long solution(int w, int h) {
        long answer = 1;
        long big = gcd(w,h);
        long weight = (long)w;
        long height = (long)h;

        answer = weight*height - ((weight/big)+(height/big)-1)*big;

        return answer;
    }

    public static long gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return (long)a;
    }
}
</code></pre>
<hr>
<h3 id="개인-소감">개인 소감</h3>
<p>규칙성을 찾는데 시간이 걸렸다. 매번 코딩 테스트 문제만 풀이하면서는 &#39;어떤 라이브러리의 어떤 메소드를 사용하면 좋을까?&#39; 이런 고민들을 많이 했었다. 너무나도 많은 라이브러리에 메소드를 기억하기 어려워서 찾아서 공부하고, 찾아서 코드에 작성하는 형식이였다. 이번 문제는 다른 문제들과 다르게 오랜만에 라이브러리와 메소드를 고민하는 것보단 고등학교 시절로 돌아가 수학 문제를 풀던 때로 돌아간 기분이였다. 뭐든 안하면 잊어버리고 머리가 굳기 마련이다. 굳지 않고 말랑말랑한 상태 유지를 위해서 열심히 노력하자!!</p>
<hr>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/3192bb25-ca03-4bb8-8855-390e3730e93e/image.png"  width="40%" height="40%"><p></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 기능개발 Java]]></title>
            <link>https://velog.io/@00kang_jh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C-Java</link>
            <guid>https://velog.io/@00kang_jh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EA%B8%B0%EB%8A%A5%EA%B0%9C%EB%B0%9C-Java</guid>
            <pubDate>Thu, 03 Oct 2024 11:26:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 문제는 프로그래머스 Level 2 <a href="https://school.programmers.co.kr/learn/courses/30/lessons/42586">기능개발</a> 입니다.</p>
</blockquote>
<h3 id="문제-설명">문제 설명</h3>
<p>프로그래머스 팀에서는 기능 개선 작업을 수행 중입니다. 각 기능은 진도가 100%일 때 서비스에 반영할 수 있습니다.</p>
<p>또, 각 기능의 개발속도는 모두 다르기 때문에 뒤에 있는 기능이 앞에 있는 기능보다 먼저 개발될 수 있고,
이때 뒤에 있는 기능은 앞에 있는 기능이 배포될 때 함께 배포됩니다.</p>
<p>먼저 배포되어야 하는 순서대로 작업의 진도가 적힌 정수 배열 progresses와 각 작업의 개발 속도가 적힌 정수 배열 speeds가 주어질 때 각 배포마다 몇 개의 기능이 배포되는지를 return 하도록 solution 함수를 완성하세요.</p>
<hr>
<h3 id="제한-사항">제한 사항</h3>
<ol>
<li>작업의 개수(progresses, speeds배열의 길이)는 100개 이하입니다.</li>
<li>작업 진도는 100 미만의 자연수입니다.</li>
<li>작업 속도는 100 이하의 자연수입니다.</li>
<li>배포는 하루에 한 번만 할 수 있으며, 하루의 끝에 이루어진다고 가정합니다. 예를 들어 진도율이 95%인 작업의 개발 속도가 하루에 4%라면 배포는 2일 뒤에 이루어집니다.</li>
</ol>
<hr>
<h3 id="입출력-예시">입출력 예시</h3>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/a664ad5e-51fd-4b09-b579-062dc0e2c51e/image.png" alt=""></p>
<p>progresses: 현재 진행률
speeds: 하루당 개발 속도
return: 배포 당시 구현 완료된 기능의 수</p>
<hr>
<h3 id="문제-요약">문제 요약</h3>
<p>progresses의 순서는 우선순위를 의미하기 때문에 바꾸지 않고, 앞에 있는 기능 개발이 완료되어야 뒤에 있는 기능들도 같이 배포할 수 있습니다. 각기 다른 진행 속도를 갖기 때문에 뒤에 있는 것이 모두 진행이 완료되어도 앞에 있는 기능이 완료되지 않으면 배포가 불가능합니다.</p>
<hr>
<h3 id="문제-풀이-전-생각-정리">문제 풀이 전 생각 정리</h3>
<ul>
<li>큐를 이용해서 풀면 될꺼 같습니다. 큐의 성질인 (FIFO)를 이용합니다다.</li>
</ul>
<ol>
<li>progresses의 값을 큐에 넣고 speeds를 기반으로 증가를 시켜줍니다.</li>
<li>100이 넘는 숫자들은 remove 하고, answer의 값을 증가시킵니다.</li>
<li>이 과정을 반복 후, answer를 return 합니다.</li>
</ol>
<hr>
<h3 id="코드">코드</h3>
<pre><code class="language-java">import java.util.LinkedList;
import java.util.Queue;
import java.util.ArrayList;

class Solution {
    public ArrayList solution(int[] progresses, int[] speeds) {
        ArrayList&lt;Integer&gt; answer = new ArrayList&lt;Integer&gt;(); //추가하는데 편의를 위해서 ArrayList 사용
        Queue&lt;Integer&gt; works = new LinkedList&lt;&gt;();

        // Queue에 progresses값으로 100%까지 걸리는 일수 저장하기
        for(int i =0; i&lt; progresses.length; i++){ 
            float cal = (100-progresses[i])/speeds[i];
            if((100.0-progresses[i])%speeds[i] != 0) cal++;
            works.offer((int)cal);
        }

        // 배포를 위한 검사 시작
        while(!works.isEmpty()){
            int work = works.poll(); // 제일 앞에 있는 것을 하나 꺼냄.
            int count = 1;

            //work보다 작은 것들의 수를 셈. 배포할 때, 몇개의 기능을 배포할 수 있는지 파악하기 위해서
            while(!works.isEmpty()){
                if(work &lt; works.peek()) break; // work보다 크면 그 기능 뒤부터는 배포 불가능해서 종료함.
                works.remove();
                count++;
            }
            answer.add(count); // answer에 추가해줌.
        }

        works.clear();
        return answer;
    }
}</code></pre>
<hr>
<h3 id="개인-소감">개인 소감</h3>
<p>난이도는 level2 치고는 어렵지 않았던 것 같다. 풀이를 생각하는데 어려움이 있었던 건 아니고 아무래도 Queue, ArrayList, LinkedList와 같이 자바 내장 라이브러리를 사용을 해야해서 level2가 아닌가 싶었다. 그리고 큐를 사용하지 않고 문제를 풀이할 수 있을거 같다는 생각이 들긴 하였다. 나중에 시간이 되면 배열로도 한번 풀어봐야겠다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/d3231d96-f723-478f-b859-f18a02e7890f/image.png"  width="20%" height="20%"><p></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 문자열 압축 Java]]></title>
            <link>https://velog.io/@00kang_jh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%95%95%EC%B6%95-Java</link>
            <guid>https://velog.io/@00kang_jh/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%95%95%EC%B6%95-Java</guid>
            <pubDate>Thu, 26 Sep 2024 17:46:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이번 문제는 2020 KAKAO BLIND RECRUITMENT에서 출제되었고 level 2 <a href="https://school.programmers.co.kr/learn/courses/30/lessons/60057">문자열 압축</a> 입니다.</p>
</blockquote>
<h4 id="문제-설명">문제 설명</h4>
<p>데이터 처리 전문가가 되고 싶은 &quot;어피치&quot;는 문자열을 압축하는 방법에 대해 공부를 하고 있습니다. 최근에 대량의 데이터 처리를 위한 간단한 비손실 압축 방법에 대해 공부를 하고 있는데, <strong>문자열에서 같은 값이 연속해서 나타나는 것을 그 문자의 개수와 반복되는 값으로 표현하여 더 짧은 문자열</strong>로 줄여서 표현하는 알고리즘을 공부하고 있습니다.
간단한 예로 &quot;aabbaccc&quot;의 경우 &quot;2a2ba3c&quot;(문자가 반복되지 않아 한번만 나타난 경우 1은 생략함)와 같이 표현할 수 있는데, 이러한 방식은 반복되는 문자가 적은 경우 압축률이 낮다는 단점이 있습니다. 예를 들면, &quot;abcabcdede&quot;와 같은 문자열은 전혀 압축되지 않습니다. &quot;어피치&quot;는 이러한 단점을 해결하기 위해 문자열을 1개 이상의 단위로 잘라서 압축하여 더 짧은 문자열로 표현할 수 있는지 방법을 찾아보려고 합니다.</p>
<p>예를 들어, &quot;ababcdcdababcdcd&quot;의 경우 문자를 1개 단위로 자르면 전혀 압축되지 않지만, 2개 단위로 잘라서 압축한다면 &quot;2ab2cd2ab2cd&quot;로 표현할 수 있습니다. 다른 방법으로 8개 단위로 잘라서 압축한다면 &quot;2ababcdcd&quot;로 표현할 수 있으며, 이때가 가장 짧게 압축하여 표현할 수 있는 방법입니다.</p>
<p>다른 예로, &quot;abcabcdede&quot;와 같은 경우, 문자를 2개 단위로 잘라서 압축하면 &quot;abcabc2de&quot;가 되지만, 3개 단위로 자른다면 &quot;2abcdede&quot;가 되어 3개 단위가 가장 짧은 압축 방법이 됩니다. 이때 3개 단위로 자르고 마지막에 남는 문자열은 그대로 붙여주면 됩니다.</p>
<p>압축할 문자열 s가 매개변수로 주어질 때, 위에 설명한 방법으로 1개 이상 단위로 문자열을 잘라 압축하여 표현한 문자열 중 가장 짧은 것의 길이를 return 하도록 solution 함수를 완성해주세요.</p>
<hr>
<h4 id="제한-사항">제한 사항</h4>
<p>s의 길이가 1000이하, 모두 소문자입니다.</p>
<hr>
<h4 id="입출력-예시">입출력 예시</h4>
<body>
<p align="center"><img src="https://velog.velcdn.com/images/00kang_jh/post/05e1acd7-17e6-4202-b6b0-3a2b5461ed6c/image.png"  width="50%" height="50%"><p>
</body>

<hr>
<h4 id="문제-요약문제가-너무-길어">문제 요약(문제가 너무 길어,,,)</h4>
<p>Main point 반복되는 문자를 &quot;반복횟수 + 반복되는 문자&quot;로 대체하는데 최대한 짧게 만드는 것이다.</p>
<hr>
<h4 id="문제-풀이-전-생각-정리">문제 풀이 전 생각 정리</h4>
<ol>
<li>최소 위해서 최대로 초기화한다. answer를 문자열의 길이로 정하고 시작한다.</li>
<li>글자를 1 ~ s.length()/2로 순차적으로 잘라서 검사를 진행한다.</li>
</ol>
<p>-&gt; substring 사용하면 될 듯
3. 잘라서 검색하다가 반복이 되지 않으면 &quot;반복횟수 + 반복되는 문자&quot;로 저장한다. 
-&gt; 최소로 만드는 것이 목표이기 때문에 s를 다시 재사용 가능성 있음
4. 길이가 1인 경우는 그냥 return 1 처리</p>
<p>❗️ 굳이 문자열로 저장해서 문제를 풀어야할까?❗️
아무래도 문자열이 무거우니깐 단순 검사 통해서 하면 어떨까?</p>
<hr>
<h4 id="코드">코드</h4>
<pre><code class="language-java">import java.util.ArrayList;

class Solution {

    public int press(String str, int length) {
        StringBuilder sb = new StringBuilder(); // 줄인 문자열을 저장.

        String last = &quot;&quot;;
        int count = 0;
        for (int start = 0; start &lt; str.length(); start += length) {
            int end = start + length;

            if (end &gt; str.length()) end = str.length(); //길이가 오버돼서 에러나는 경우

            String token = str.substring(start, end); //압축할 단위

            if (token.equals(last)) count++;
            else {
                if (count &gt; 1) sb.append(count);
                sb.append(last);
                last = token;
                count = 1;
            }
        }
        if (count &gt; 1) sb.append(count); //1은 반복이 아니기 때문에 1보다 큰 값일 경우
        sb.append(last);

        return sb.length();
    }

    public int solution(String s) {
        int answer = s.length();
        if (s.length() == 1) return 1;

        for (int length = 1; length &lt;= s.length()/2; length++) {
            int pressed = press(s, length); // 압축했을 때 문자열의 길이
            if (pressed &lt; answer) { // 최소값 찾기
                answer = pressed;
            }
        }

        return answer;
    }
}</code></pre>
<hr>
<h4 id="개인-소감">개인 소감</h4>
<p>코딩 테스트 준비한지 얼마 되지 않아서 생각하는 것도 구현하는 것도 미숙한 부분들이 많다는 느낌을 많이 받았다. 그리고 문제 풀고 나서 개인적으로 문자열을 새롭게 만들어서 결과를 구하는 방법이 메모리 측면에서 비효율적이라는 생각이 들었다. 추후 문자열로 저장하지 않고 단순 검사를 통해서 결과값을 구해볼 예정이다.</p>
<p>to be continue,,,,</p>
<hr>
<p><img src="https://velog.velcdn.com/images/00kang_jh/post/abd765c7-2cc7-4960-bab7-632ac2236f8b/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>