<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>박가은</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 19 May 2025 11:29:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>박가은</title>
            <url>https://velog.velcdn.com/images/developer_gaeun/profile/ebadc4d3-6b3f-4b4d-8bcd-249b23f63773/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 박가은. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/developer_gaeun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ORM 이 있는데 SQL 공부해야 됨?ㅋ]]></title>
            <link>https://velog.io/@developer_gaeun/%EC%A0%95%EB%B3%B4%ED%86%B5%EC%8B%A0%EA%B3%BC%EC%A0%9C2</link>
            <guid>https://velog.io/@developer_gaeun/%EC%A0%95%EB%B3%B4%ED%86%B5%EC%8B%A0%EA%B3%BC%EC%A0%9C2</guid>
            <pubDate>Mon, 19 May 2025 11:29:07 GMT</pubDate>
            <description><![CDATA[<p>백엔드 개발은 데이터베이스와 밀접한 관련이 있습니다. 데이터를 생성하고, 읽고, 수정하고, 삭제하는 CRUD 기능은 대부분의 백엔드 개발의 핵심이며, 이 과정에서 데이터베이스와의 상호작용이 활발하게 이루어집니다. 이 과정에서 백엔드 개발자에게는 두 가지 접근 방식이 존재합니다. 바로 SQL 과 ORM 입니다.</p>
<p><img src="https://velog.velcdn.com/images/developer_gaeun/post/346ff397-07fc-4736-8020-0f6be7d01401/image.png" alt=""></p>
<h3 id="sql-은-무엇일까요">SQL 은 무엇일까요?</h3>
<p>SQL, Structured Query Language 는 관계형 데이터베이스를 조작하기 위한 언어입니다. 사용자가 원하는 데이터를 정확하게 조회하거나 삽입하고, 효율적인 쿼리를 통해 시스템의 성능을 관리하는 데 핵심적인 역할을 합니다.</p>
<pre><code class="language-java">String sql = &quot;INSERT INTO user (username, email) VALUES (?, ?)&quot;;
jdbcTemplate.update(sql, &quot;name&quot;, &quot;email&quot;);</code></pre>
<h3 id="orm-은-무엇일까요">ORM 은 무엇일까요?</h3>
<p>ORM, Object Relational Mapping 은 객체지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블을 자동으로 매핑해주는 도구 입니다. 예시로는 Java 의 JPA, Hibernate, Python 의 SQLAlchemy 등이 있습니다.</p>
<pre><code class="language-java">userRepository.save(new User(&quot;name&quot;, &quot;email&quot;));</code></pre>
<hr>
<p>아래의 예제 코드만 보면 ORM 을 사용하는 것이 훨씬 간편하고 직관적으로 보입니다. 이뿐만 아니라 ORM 을 사용하면 여러가지 추가적인 장점이 존재합니다.</p>
<ul>
<li>객체 조작만으로 데이터베이스 연동이 가능 → 반복적으로 SQL 을 작성할 필요가 없음</li>
<li>테이블 변경 시 도메인 모델만 변경하면 됨 → 일관성 유지 가능</li>
<li>SQL Injection 등 보안 이슈 예방 가능 (바인딩 처리)</li>
</ul>
<blockquote>
<p>ORM 은 위와 같이 다양한 장점을 가지고 있으며, 개발 속도와 코드 가독성을 높여주는 강력한 도구 입니다.</p>
</blockquote>
<p>이렇게 보면 ORM 은 SQL 을 대신할 수 있는 완벽한 대체 수단으로 보입니다. 하지만 사실은 그렇지 않습니다. 이제 ORM 의 한계와 단점을 알아보겠습니다.</p>
<h3 id="orm-의-한계와-단점">ORM 의 한계와 단점</h3>
<ul>
<li>연관 객체 조회 시 반복 쿼리 발생 → 성능 저하</li>
<li>다중 조인, 서브쿼리 등은 ORM 으로 표현하기 어려움</li>
<li>어떤 SQL 이 실행되었는지 명확하지 않아서 쿼리 추적이 어려움 → 디버깅 난이도 상승</li>
</ul>
<p>또한 실무에서도 대규모 데이터를 다루는 프로젝트에서는 ORM 의 추상화가 오히려 걸림돌이 됩니다. 실제로 많은 기업들이 대량 조회에는 ORM 을 사용하지 않습니다.</p>
<h3 id="결론적으로">결론적으로</h3>
<p>백엔드 개발자는 ORM 뿐만 아니라 SQL 에 관한 충분한 사용 경험과 이해도 필요합니다.</p>
<ul>
<li>데이터 조회 최적화: 특정 조건에 맞는 데이터만 빠르게 추출해야 할 때</li>
<li>대량 삽입/ 갱신 처리: 트랜잭션 처리와 배치 작업에 유리
<span style="color:gray">☞ 트랜잭션: DBMS 에서 데이터를 다루는 논리적인 작업의 단위
☞ 트랜잭션 처리: 트랜잭션 각 단위가 원자성, 일관성, 고립성, 지속성의 성질을 유지할 수 있도록 처리하는 것
☞ 배치 작업: 사용자 개입 없이, 정해진 시간이나 조건에 따라 반복적으로 실행되는 일괄 처리 작업</span></li>
<li>다중 조인, 서브쿼리 등을 통해 복잡한 처리 가능</li>
</ul>
<hr>
<p>결국 ORM 도 SQL 을 기반으로 만들어진 것이기때문에ORM을 제대로 이해하고 사용하기 위해서는 SQL 에 대한 선행 학습이 우선되어야합니다.</p>
<blockquote>
<p>결론적으로 SQL 은 단순한 기술이 아니라 백엔드 시스템을 이해하고 제어하기 위한 중요한 기본 지식입니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA["단단한" 객체 지향 설계를 위해 고려해야 할 5가지 원칙 - SOLID 원칙]]></title>
            <link>https://velog.io/@developer_gaeun/%EB%B0%B1%EC%97%94%EB%93%9C%ED%8A%B8%EB%9E%99%EC%88%98%EC%97%85%EA%B3%BC%EC%A0%9C3</link>
            <guid>https://velog.io/@developer_gaeun/%EB%B0%B1%EC%97%94%EB%93%9C%ED%8A%B8%EB%9E%99%EC%88%98%EC%97%85%EA%B3%BC%EC%A0%9C3</guid>
            <pubDate>Mon, 21 Apr 2025 14:30:44 GMT</pubDate>
            <description><![CDATA[<p>SOLID 원칙은 <strong>객체 지향 설계의 5가지 핵심 원칙</strong> 입니다.
이 원칙을 따르면 <strong>유지 보수성</strong>과 <strong>확장성 측면</strong>에서 더 나은 설계를 할 수 있습니다.</p>
<p>이제 5가지 원칙에 대해서 그 원칙이 무엇이고, 어떻게 지켜야하고, 위반한, 잘 지킨 예시는 어떤것이 있는지 하나하나 알아보겠습니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>약어</th>
<th>영문명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>S</td>
<td>SRP</td>
<td>Single Responsibility Principle</td>
<td>단일 책임 원칙</td>
</tr>
<tr>
<td>O</td>
<td>OCP</td>
<td>Open Closed Principle</td>
<td>개방 폐쇄 원칙</td>
</tr>
<tr>
<td>L</td>
<td>LSP</td>
<td>Liskov Substitution Principle</td>
<td>리스코프 치환 원칙</td>
</tr>
<tr>
<td>I</td>
<td>ISP</td>
<td>Interface Segregation Principle</td>
<td>인터페이스 분리 원칙</td>
</tr>
<tr>
<td>D</td>
<td>DIP</td>
<td>Dependency Inversion Principle</td>
<td>의존 역전 원칙</td>
</tr>
</tbody></table>
<hr>
<h2 id="원칙1-s">원칙1 &#39;S&#39;</h2>
<blockquote>
<p>SRP - Single Responsibility Principle
단일 책임 원칙: 클래스는 <strong>하나의 책임</strong> 만 가져야 하며, 변경 이유도 하나여야 한다.</p>
</blockquote>
<p>이 말은 한 클래스가 여러 이유로 변경되지 않도록 해야한다는 의미입니다.</p>
<p>예를 들어서</p>
<pre><code class="language-java">@Service
public class UserService {
    public void register(User user) {
        validateUser(user);   // 1. 유효성 검증
        userRepository.save(user); // 2. 사용자 저장
        mailService.sendWelcomeMail(user.getEmail()); // 3. 이메일 발송
    }
}</code></pre>
<p>이 코드에서는 <code>UserService</code> 가 바뀌는 이유가 유효성 검사 검증 기준이 변경되거나, 사용자 저장 방식이 바뀌거나 등등 다양합니다.
→ 따라서 이 클래스는 SRP, 단일 책임 원칙을 지키고 있지 않습니다.</p>
<p>그러므로 코드를 아래와 같이 개선할 수 있습니다.</p>
<pre><code class="language-java">@Service
public class UserService {
    private final UserValidator validator;
    private final MailService mailService;

    public void register(User user) {
        validator.validate(user);
        userRepository.save(user);
        mailService.sendWelcomeMail(user.getEmail());
    }
}</code></pre>
<ul>
<li><code>UserValidator</code> : 유효성 검사 책임</li>
<li><code>UserRepository</code> : 데이터 저장 책임</li>
<li><code>MailService</code> : 이메일 발송 책임</li>
</ul>
<p>→ 각각은 하나의 이유로만 변경됨 → SRP 준수</p>
<h2 id="원칙2-o">원칙2 &#39;O&#39;</h2>
<blockquote>
<p>OCP - Open Closed Principle
개방 폐쇄 원칙: 소프트웨어 요소는 <strong>확장에는 열려 있고, 수정에는 닫혀 있어야</strong> 한다.</p>
</blockquote>
<p>예시와 함께 OCP 에 대해서 더 알아보겠습니다.</p>
<pre><code class="language-java">public class DiscountService {
    public int getDiscountPrice(String grade, int price) {
        if (&quot;NORMAL&quot;.equals(grade)) {
            return price - 1000;
        } else if (&quot;VIP&quot;.equals(grade)) {
            return price - 3000;
        } else {
            return price;
        }
    }
}</code></pre>
<p>이런 코드라면 새로운 할인 정책이 추가될 때마다 <code>if-else문</code> 을 추가하는 번거로움을 겪게 됩니다. <strong>확장에는 열려 있고 수정에는 닫혀 있어야</strong> 하는 OCP 를 위반하게 된 것이죠.</p>
<p>따라서 아래와 같이 코드를 개선할 수 있습니다.</p>
<pre><code class="language-java">interface PaymentMethod {
    void pay();
}

class CardPayment implements PaymentMethod {
    @Override
    public void pay() {
        // 카드 결제 처리
    }
}

class CashPayment implements PaymentMethod {
    @Override
    public void pay() {
        // 현금 결제 처리
    }
}

class PaymentProcessor {
    void pay(PaymentMethod method) {
        method.pay();
    }
}</code></pre>
<ul>
<li><code>PaymentMethod</code> 라는 인터페이스를 통해 결제 방식 추가가 자유롭습니다.</li>
<li>새로운 결제 수단이 생겨도 <code>PaymentProcessor</code> 는 수정할 필요가 없습니다.
→ OCP 를 만족하는 구조</li>
</ul>
<h2 id="원칙3-l">원칙3 &#39;L&#39;</h2>
<blockquote>
<p>LSP - Likov Substituition Principle
리스코프 치환 원칙: 하위 클래스는 상위 클래스를 완벽하게 대체할 수 있어야 한다.</p>
</blockquote>
<p>이 의미는 부모 클래스 객체를 사용하는 곳에 자식 클래스 객체를 넣어도 동일하게 작동해야 한다는 의미입니다. 자식 클래스가 부모의 규칙을 깨거나 예외를 발생시키면 LSP 가 위반됩니다. 아래의 예시와 함께 더 자세히 알아보겠습니다.</p>
<pre><code class="language-java">class Bird {
    public void fly() {
        // 날 수 있음
    }

    public void layEgg() { 
        // 알 낳기 
    }
}

class Chicken extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException(&quot;닭은 날 수 없습니다.&quot;);
    }
}
</code></pre>
<ul>
<li><code>Bird</code> 는 <code>fly()</code> 가 가능한 객체로 설계됨 (부모 클래스의 규칙)</li>
<li><code>Chicken</code> 는 <code>Bird</code> 를 상속하지만 <code>fly()</code> 불가능 (= 자식 클래스가 부모 클래스의 규칙을 깸)
→ <code>Bird</code> 를 사용하는 코드에서 <code>Chicken</code> 를 넣으면 에러 발생
→ LSP 위반</li>
</ul>
<p>따라서 아래와 같이 코드를 개선할 수 있습니다.</p>
<pre><code class="language-java">interface Bird {
    void layEgg();
}

interface Flyable {
    void fly();
}

class Parrot implements Bird, Flyable {
    public void layEgg() { 
        // 알 낳기 
    }

    public void fly() { 
        // 날기 
    }
}

class Chicken implements Bird {
    public void layEgg() { 
        // 알 낳기 
    }
}</code></pre>
<ul>
<li><code>Bird</code> 는 공통 기능인 <code>layEgg()</code> 만 포함합니다.</li>
<li><code>Flyable</code> 은 날 수 있는 새에만 필요한 기능인 <code>fly()</code> 를 분리합니다.</li>
<li><code>Chicken</code> 는 둘 다 구현 → 날 수 있는 새</li>
<li><code>Chicken</code> 는 <code>Bird</code> 만 구현 → 날 수 없는 새
→ LSP 를 만족하는 코드</li>
</ul>
<h2 id="원칙4-i">원칙4 &#39;I&#39;</h2>
<blockquote>
<p>ISP - Interface Segregation Principle
인터페이스 분리 원칙: 클라이언트는 <strong>자신이 사용하지 않는 인터페이스에 의존하지 않아야</strong> 한다.</p>
</blockquote>
<p>이 원칙은 하나의 거대한 인터페이스보다, 작고 명확한 인터페이스 여러 개로 나누는 것이 좋다는 의미를 포함하고 있습니다.</p>
<p>아래의 예시와 함께 더 자세히 알아보겠습니다.</p>
<pre><code class="language-java">interface Worker {
    void work();
    void eat();
}

class Robot implements Worker {
    public void work() { }
    public void eat() {
        throw new UnsupportedOperationException(&quot;로봇은 밥 안 먹음&quot;);
    }
}</code></pre>
<ul>
<li><code>Robot</code> 은 <code>eat()</code> 이 필요 없음</li>
<li>하지만 <code>Worker</code> 를 구현하므로 <code>eat()</code> 을 구현해야 함
→ ISP 위반</li>
</ul>
<p>따라서 아래와 같이 코드를 개선할 수 있습니다.</p>
<pre><code class="language-java">interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Human implements Workable, Eatable {
    public void work() { }
    public void eat() { }
}

class Robot implements Workable {
    public void work() { }
}</code></pre>
<ul>
<li>각 기능을 인터페이스로 분리</li>
<li>각 클래스는 자신이 필요한 인터페이스만 구현
→ ISP 를 만족하는 코드</li>
</ul>
<h2 id="원칙5-d">원칙5 &#39;D&#39;</h2>
<blockquote>
<p>DIP - Dependency Injection Principle
의존 역전 원칙: <strong>고수준 모듈은 저수준 모듈에 의존하면 안된다.</strong> 둘 다 추상화에 의존해야 한다.</p>
</blockquote>
<p>DIP 를 위반하면, 고수준 모듈(비즈니스 로직)이 저수준 모듈(구현 클래스)에 직접 의존하게 되고 이러면 변경에 취약해집니다. 따라서 인터페이스(추상화) 를 통해 서로 느슨하게 연결해야 합니다.</p>
<p>아래의 예시와 함께 더 자세히 알아보겠습니다.</p>
<pre><code class="language-java">class MySQLDatabase {
    public void save(String data) {
        // MySQL에 저장
    }
}

class DataService {
    private MySQLDatabase db = new MySQLDatabase();

    public void save(String data) {
        db.save(data);
    }
}</code></pre>
<ul>
<li><code>DataService</code> (비지니스 로직) 는 <code>MySQLDatabase</code> (구현 클래스) 에 직접 의존</li>
<li>MySQL에서 다른 DB로 교체하려면 <code>DataService</code> 도 수정해야 함
→ DIP 위반</li>
</ul>
<p>따라서 아래와 같이 코드를 개선할 수 있습니다.</p>
<pre><code class="language-java">interface Database {
    void save(String data);
}

class MySQLDatabase implements Database {
    public void save(String data) {
        // MySQL에 저장
    }
}

class DataService {
    private final Database db;

    public DataService(Database db) {
        this.db = db;
    }

    public void save(String data) {
        db.save(data);
    }
}</code></pre>
<ul>
<li><code>Database</code> 라는 인터페이스에 의존 → 실제 구현은 외부에서 주입</li>
<li>MySQL, MongoDB 등 다양한 구현체를 쉽게 교체 가능 → DIP 준수</li>
</ul>
<hr>
<h3 id="solid-원칙은-꼭-지켜야-하는-것일까">SOLID 원칙은 꼭 지켜야 하는 것일까?</h3>
<p>SOLID 원칙은 객체 지향 설계에 있어서 <strong>&quot;필수 규칙&quot;</strong> 이 아니라, <strong>&quot;권장되는 설계 지침&quot;</strong> 입니다.
이 말은 SOLID 원칙을 <strong>반드시 따라야 하는 것은 아니라는 의미</strong> 입니다.</p>
<p><strong>첫번재 이유</strong>
프로젝트 규모가 작거나 단기간에 끝나는 프로젝트라면, SOLID 원칙을 엄격히 적용하면, 과설계가 발생할 수 있습니다.</p>
<p><strong>두번재 이유</strong>
모든 원칙을 다 지키려고 하면 코드 구조가 복잡해져서 협업이나 유지보수가 어려워질 수 있습니다.</p>
<p><strong>결론</strong>
SOLID 원칙을 모두 적용하려면 <strong>많은 시간과 노력이 요구</strong>됩니다.
따라서 성능, 유지보수성 등의 우선 순위를 고려하여 이에 따라 필요한 정도로 적당히 원칙을 적용하는 것이 중요합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드를 Explicit 하게 작성해달라고요? 그게 뭔데요..?]]></title>
            <link>https://velog.io/@developer_gaeun/%EB%B0%B1%EC%97%94%EB%93%9C%ED%8A%B8%EB%9E%99%EA%B3%BC%EC%A0%9C1</link>
            <guid>https://velog.io/@developer_gaeun/%EB%B0%B1%EC%97%94%EB%93%9C%ED%8A%B8%EB%9E%99%EA%B3%BC%EC%A0%9C1</guid>
            <pubDate>Mon, 14 Apr 2025 13:20:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/developer_gaeun/post/47b86c6c-f32e-4c78-9088-ae68c23a3f88/image.png" alt=""></p>
<h2 id="explicit-implicit-그게-뭔데요">Explicit, Implicit 그게 뭔데요?</h2>
<p>두 단어는 각각 한국말로 Explicit 은 <code>명시적인</code> Implicit 은 <code>묵시적인</code> 이라는 뜻을 가지고 있습니다.</p>
<blockquote>
<p>명시적 : 내용이나 뜻을 분명하게 드러내 보이는 것
묵시적: 직접적으로 말이나 행동으로 드러내지 않고 은연중에 뜻을 나타내 보이는 것</p>
</blockquote>
<p>한국말로 했을 때 뜻풀이는 다음과 같습니다.</p>
<p>따라서 <code>Explicit 한 코드</code> 는 <code>내용이나 뜻이 분명하게 드러난 코드</code>,
<code>Implicit 한 코드</code> 는 <code>직접적으로 내용이나 뜻을 드러내지 않고, 은연중에 보이는 코드</code> 라고도 할 수 있습니다.</p>
<p>이걸 그냥 말로만 하면 당연히 이해가 어렵겠죠! 아래에서 예시와 함께 알아보겠습니다.</p>
<hr>
<h2 id="--plicit-하게-코드-작성하기">--plicit 하게 코드 작성하기</h2>
<h3 id="01---plicit-한-변수명-작성하기">01 --plicit 한 변수명 작성하기</h3>
<p>Explicit 한 변수명 예시</p>
<pre><code class="language-python">user_age = 25</code></pre>
<blockquote>
<p>변수가 어떤 데이터를 담고 있는지 명확하게 알 수 있음</p>
</blockquote>
<p>Implicit 한 변수명 예시</p>
<pre><code class="language-python">x = 25</code></pre>
<blockquote>
<p>변수명이 추상적이라 코드를 처음 보는 사람이 이해하기 어려움</p>
</blockquote>
<h3 id="02---plicit-한-함수명-작성하기">02 --plicit 한 함수명 작성하기</h3>
<p>Explicit 한 함수명 예시</p>
<pre><code class="language-python">def calculate_total_price(items):</code></pre>
<blockquote>
<p>함수가 어떤 역할을 하는지 함수명만 보고도 유추할 수 있음</p>
</blockquote>
<p>Implicit 한 함수명 예시</p>
<pre><code class="language-python">def handle_data(data):</code></pre>
<blockquote>
<p>함수가 정확히 어떤 일을 하는지 이름만으로 알기 어려움</p>
</blockquote>
<hr>
<h2 id="--plicit-한-코드의-장단점">--plicit 한 코드의 장단점</h2>
<h3 id="explicit-한-코드의-장점">Explicit 한 코드의 장점</h3>
<ul>
<li>누구나 코드를 읽고 이해하기 쉬움</li>
<li>시간이 지나도 의도를 파악하기 쉬움</li>
<li>팀원 간 커뮤니케이션 비용 절감</li>
</ul>
<h3 id="explicit-한-코드의-단점">Explicit 한 코드의 단점</h3>
<ul>
<li>코드 길이가 늘어남</li>
<li>개발 속도 저하</li>
</ul>
<h3 id="implicit-한-코드의-장점">Implicit 한 코드의 장점</h3>
<ul>
<li>짧고 깔금한 코드 작성 가능</li>
<li>빠르게 아이디어를 구현할 때 유리함</li>
</ul>
<h3 id="implicit-한-코드의-단점">Implicit 한 코드의 단점</h3>
<ul>
<li>다른 사람이 코드를 해석할 때 어려움을 겪음</li>
<li>맥락에 대해, 각 변수 또는 함수가 하는 역할을 오해해 잘못된 사용의 발생 가능성</li>
<li>팀원 간 오해 발생 가능</li>
</ul>
<blockquote>
<p>팀 프로젝트 -&gt; Explicit 한 코드
혼자 코테 문제를 풀 때/ 숏코딩 -&gt; Implicit 한 코드</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹서버? WAS? API서버? RestAPI? RestfulAPI? 뭐가 다른건데????]]></title>
            <link>https://velog.io/@developer_gaeun/%EB%B0%B1%EC%97%94%EB%93%9C%ED%8A%B8%EB%9E%99%EA%B3%BC%EC%A0%9C2</link>
            <guid>https://velog.io/@developer_gaeun/%EB%B0%B1%EC%97%94%EB%93%9C%ED%8A%B8%EB%9E%99%EA%B3%BC%EC%A0%9C2</guid>
            <pubDate>Mon, 14 Apr 2025 12:29:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>웹서버 WAS API서버 RestAPI RestfulAPI</p>
</blockquote>
<p>웹 개발을 공부하다보면 자주 마주치는 개념들 입니다.
비슷해보이지만 엄연히 다른 이 개념들을 헷갈리지 않게 각각의 개념부터 비슷한 두 개념을 비교해가며 설명해드리겠습니다.</p>
<hr>
<h2 id="개념-설명">개념 설명</h2>
<h3 id="01-웹서버-web-server">01 웹서버 (Web Server)</h3>
<blockquote>
<p>정적인 파일 (HTML, CSS 등 -&gt; 즉시 응답 가능한 컨텐츠) 을 클라이언트에게 전달해주는 서버</p>
</blockquote>
<ul>
<li>웹서버는 사용자가 웹사이트에 접속했을 때 필요한 정적 자원을 빠르게 제공</li>
<li>단순히 요청을 받고 해당 파일을 찾아 응답하는 구조 -&gt; 처리 속도가 빠름</li>
<li>대표적인 웹서버 : Apache HTTP Sever, Nginx</li>
<li>주로 프론트엔드 정적 리소스 제공에 사용됨</li>
</ul>
<h3 id="02-was-web-application-server">02 WAS (Web Application Server)</h3>
<blockquote>
<p>동적인 웹 페이지를 생성하기 위해 로직을 처리하는 서버</p>
</blockquote>
<ul>
<li>웹서버 단독으로 처리할 수 없는 데이터베이스 조회나 다양한 로직 처리가 필요한 동적 컨텐츠 제공</li>
<li>백엔드 로직이 실행되는 공간</li>
<li>주로 데이터베이스 서버와 같이 실행됨</li>
<li>대표적인 WAS : Tomcat, WildFly, Jetty</li>
<li>DB 연동, 세션 처리, 로직 수행 등을 담당</li>
</ul>
<h3 id="03-api-서버">03 API 서버</h3>
<blockquote>
<p>API (Application Programming Interface) : 소프트웨어 애플리케이션 간에 데이터, 기능, 특징을 교환할 수 있도록 하는 규칙이나 프로토콜</p>
</blockquote>
<blockquote>
<p>프론트엔드나 외부 시스템에 데이터를 전달하는 백엔드 서버</p>
</blockquote>
<ul>
<li>예전에는 서버가 HTML 까지 만들어서 보냈지만, 최근엔 프론트엔드 담당자가 UI 를 만들고 백엔드 담당자가 API 서버를 통해 JSON 형태의 데이터만 제공하는 방식이 일반적 (= 데이터 위주의 응답)</li>
</ul>
<h3 id="04-restapi">04 RestAPI</h3>
<blockquote>
<p>REST (Representatial State Transfer) : 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미</p>
</blockquote>
<blockquote>
<p>REST 아키텍쳐 스타일을 적용한 API</p>
</blockquote>
<ul>
<li>자원을 URI 로 표현</li>
<li>HTTP 메서드를 통해 해당 자원에 대한 행위를 명확히 나타내는 방식</li>
</ul>
<h3 id="05-restfulapi">05 RestfulAPI</h3>
<blockquote>
<p>REST 규칙을 더 엄격하게 준수한 API</p>
</blockquote>
<ul>
<li>REST 아키텍쳐를 얼마나 잘 따랐는가에 따라 RestAPI 를 Restful 하다고 표현</li>
<li>URI 설계, 메서드 사용, 상태코드 응답 등을 REST 원칙에 맞게 명획하고 일관되게 구현</li>
</ul>
<hr>
<h2 id="개념-비교">개념 비교</h2>
<h3 id="01-웹서버-vs-was">01 웹서버 VS WAS</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>웹서버 (Web Server)</th>
<th>WAS (Web Application Server)</th>
</tr>
</thead>
<tbody><tr>
<td>주요 역할</td>
<td>정적 리소스 제공 (HTML, CSS, JS 등)</td>
<td>비즈니스 로직 처리</td>
</tr>
<tr>
<td>처리 방식</td>
<td>클라이언트 요청 -&gt; 파일 응답</td>
<td>클라이언트 요청 -&gt; 로직 처리 -&gt; 응답 생성</td>
</tr>
<tr>
<td>응답 형태</td>
<td>정적인 파일 그대로</td>
<td>동적으로 생성된 HTML, JSON 등</td>
</tr>
<tr>
<td>예시</td>
<td>Nginx, Apache HTTP Server</td>
<td>Tomcat, Jetty, JBoss</td>
</tr>
<tr>
<td>특징</td>
<td>빠르고 가벼움</td>
<td>상대적으로 무겁고 복잡한 처리 가능</td>
</tr>
</tbody></table>
<hr>
<h3 id="02-was-vs-api-서버">02 WAS VS API 서버</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>WAS (Web Application Server)</th>
<th>API 서버 (API Server)</th>
</tr>
</thead>
<tbody><tr>
<td>목적</td>
<td>사용자 요청에 따라 동적 페이지 렌더링</td>
<td>프론트엔드나 외부 시스템에 데이터 제공</td>
</tr>
<tr>
<td>응답 형태</td>
<td>HTML, JSP 등</td>
<td>JSON, XML 등 (주로 JSON)</td>
</tr>
<tr>
<td>포함 요소</td>
<td>비즈니스 로직 + View 렌더링</td>
<td>오직 비즈니스 로직 처리 및 데이터 응답</td>
</tr>
<tr>
<td>대상</td>
<td>브라우저 사용자</td>
<td>웹/앱 클라이언트, 외부 시스템</td>
</tr>
<tr>
<td>예시</td>
<td>Spring MVC (JSP 포함)</td>
<td>Spring Boot + REST Controller</td>
</tr>
</tbody></table>
<hr>
<h3 id="03-api-서버-vs-rest-api">03 API 서버 VS REST API</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>API 서버 (API Server)</th>
<th>REST API</th>
</tr>
</thead>
<tbody><tr>
<td>정의</td>
<td>API를 제공하는 서버 전체</td>
<td>REST 아키텍처 스타일을 따르는 API 설계 방식</td>
</tr>
<tr>
<td>범위</td>
<td>REST, RPC, GraphQL 등 모두 포함</td>
<td>REST 아키텍쳐 원칙만 해당</td>
</tr>
<tr>
<td>목적</td>
<td>다양한 방식으로 데이터 제공</td>
<td>리소스 중심의 URI + HTTP 메서드 설계</td>
</tr>
<tr>
<td>사용 방식</td>
<td>API 구현 방식은 자유로움</td>
<td>HTTP 메서드 기반(GET, POST 등)</td>
</tr>
</tbody></table>
<hr>
<h3 id="04-restapi-vs-restfulapi">04 RestAPI VS RestfulAPI</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>REST API</th>
<th>Restful API</th>
</tr>
</thead>
<tbody><tr>
<td>정의</td>
<td>REST 아키텍처 스타일을 따르는 API</td>
<td>REST 원칙을 엄격하게 지킨 REST API</td>
</tr>
<tr>
<td>규칙 충실도</td>
<td>일부만 지킬 수도 있음</td>
<td>URI, 메서드, 상태코드 등 완전한 준수</td>
</tr>
<tr>
<td>URI 설계</td>
<td>REST 스럽게 작성했지만 일관성 부족 가능</td>
<td>명확한 리소스 기반 URI 구조</td>
</tr>
<tr>
<td>응답 방식</td>
<td>HTTP 상태코드 활용이 부족할 수 있음</td>
<td>정확한 상태코드와 일관된 응답 구조 사용</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 시작 전 *반드시* 알아야 할 네트워크 상식]]></title>
            <link>https://velog.io/@developer_gaeun/%EC%A0%95%EB%B3%B4%ED%86%B5%EC%8B%A0%EA%B3%BC%EC%A0%9C</link>
            <guid>https://velog.io/@developer_gaeun/%EC%A0%95%EB%B3%B4%ED%86%B5%EC%8B%A0%EA%B3%BC%EC%A0%9C</guid>
            <pubDate>Tue, 08 Apr 2025 07:05:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/developer_gaeun/post/885434d7-f3a5-4a2a-bc64-f034d82e74a7/image.png" alt="">
개발자라면 누구나 한번쯤은 들어봤을 <strong>도커</strong> 같은 기술을 제대로 이해하려면, IP, 포트, NAT 와 같은 네트워크 기본 개념들을 제대로 알고 있어야 합니다. 또한 개발을 하다보면 네트워크에 대한 이해도가 필요해지는 순간이 옵니다.</p>
<p>따라서 지금부터 <strong>도커 네트워크를 이해하기 위해 꼭 알아야 할 핵심 네트워크 개념</strong>들에 대해서 알아보겠습니다.</p>
<hr>
<h2 id="ip-란">IP 란?</h2>
<p>아이피 (IP, Internet Protocol) 주소는 네트워크 상에서 <strong>기기 (컴퓨터, 스마트폰 등) 를 식별하기 위한 주소</strong>입니다.<br>사람에게 집 주소가 있듯, 기기에도 IP 주소가 있어야 서로 통신할 수 있습니다.</p>
<hr>
<h2 id="포트란">포트란?</h2>
<p>IP 주소가 <strong>어느 기기인지</strong>를 알려준다면, 포트 (Port) 는 <strong>그 기기 안의 어떤 프로그램인지</strong>를 식별하는 번호입니다.<br>같은 IP 주소를 가진 기기 안에서도 여러 포트 번호를 통해 다양한 서비스를 동시에 실행할 수 있습니다.</p>
<ul>
<li>예시:<ul>
<li>80번 포트: HTTP 웹 서버</li>
<li>443번 포트: HTTPS 웹 서버</li>
</ul>
</li>
</ul>
<blockquote>
<p>웹 브라우저는 URL에서 포트 번호를 명시하지 않아도, 사용하는 프로토콜에 따라 <strong>기본 포트</strong>를 자동으로 사용합니다.</p>
<ul>
<li><code>http://</code> → 80</li>
<li><code>https://</code> → 443</li>
</ul>
</blockquote>
<p><a href="https://www.youtube.com">https://www.youtube.com</a> -&gt; 정상적으로 접속 가능
<a href="https://www.youtube.com:443">https://www.youtube.com:443</a> -&gt; 정상적으로 접속 가능
<a href="https://www.youtube.com:80">https://www.youtube.com:80</a> -&gt; 맞지않는 포트번호로는 접속 불가</p>
<hr>
<h2 id="공인-ip-vs-사설-ip">공인 IP vs 사설 IP</h2>
<h3 id="공인-ip-public-ip">공인 IP (Public IP)</h3>
<ul>
<li>인터넷 상에서 전 세계적으로 <strong>유일한 주소</strong></li>
<li>인터넷 서비스 제공자 (ISP (Internet Service Provider)) 로부터 할당받음</li>
<li>외부에서 접근 가능</li>
</ul>
<h3 id="사설-ip-private-ip">사설 IP (Private IP)</h3>
<ul>
<li>내부 네트워크 (예: 집, 회사, 학교 등) 에서만 사용되는 IP 주소</li>
<li>외부 인터넷에서는 접근 불가</li>
</ul>
<hr>
<h3 id="사설-ip-주소-대역">사설 IP 주소 대역</h3>
<p>사설 IP는 다음 범위 안에서만 사용합니다:</p>
<table>
<thead>
<tr>
<th>클래스</th>
<th>CIDR 표기</th>
<th>주소 범위</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>10.0.0.0/8</td>
<td>10.0.0.0 ~ 10.255.255.255</td>
</tr>
<tr>
<td>B</td>
<td>172.16.0.0/12</td>
<td>172.16.0.0 ~ 172.31.255.255</td>
</tr>
<tr>
<td>C</td>
<td>192.168.0.0/16</td>
<td>192.168.0.0 ~ 192.168.255.255</td>
</tr>
</tbody></table>
<hr>
<h2 id="cidr사이더-표기법이란">CIDR(사이더) 표기법이란?</h2>
<p>CIDR (Classless Inter-Domain Routing) 은 IP 주소를 표현하고 <strong>주소 범위를 나누기 위한 표기법</strong>입니다.</p>
<p>형식: <code>IP주소/숫자</code></p>
<p>예: <code>192.168.0.0/24 &lt;- 슬래쉬 뒤에 이 숫자가 prefix</code></p>
<blockquote>
<p>CIDR에서 가능한 IP 수는 아래 공식으로 계산할 수 있습니다.
<code>(가능한 IP 수) = 2 ^ (32 - (prefix 길이))</code></p>
</blockquote>
<h3 id="1921680024-에서-알-수-있는-사실"><code>192.168.0.0/24</code> 에서 알 수 있는 사실</h3>
<ul>
<li>네트워크 주소: <code>192.168.0</code></li>
<li>사용할 수 있는 호스트 수: <strong>256개 (0~255)</strong><br>(실제로는 254개만 사용 가능. 0은 네트워크 식별용, 255는 브로드캐스트용)</li>
</ul>
<hr>
<h2 id="라우터-router">라우터 (router)</h2>
<p>라우터는 <strong>내부 네트워크와 외부 인터넷을 연결</strong>해주는 장비입니다.</p>
<ul>
<li>ISP로부터 받은 <strong>공인 IP</strong>를 통해 인터넷에 연결</li>
<li>내부 장치에는 각각 <strong>사설 IP를 자동 할당</strong> </li>
<li>외부 통신 시 <strong>NAT</strong>을 사용해 사설 IP ↔ 공인 IP 변환 처리</li>
</ul>
<hr>
<h2 id="네트워크-인터페이스와-포트">네트워크 인터페이스와 포트</h2>
<ul>
<li><strong>네트워크 인터페이스</strong> : 컴퓨터나 네트워크 장비가 네트워크에 연결되어 데이터를 주고받기 위한 물리적 또는 가상의 연결 지점</li>
<li><strong>포트</strong> : 해당 인터페이스를 통해 들어오는 데이터가 <strong>어떤 프로그램으로 전달되어야 하는지</strong> 구분하는 번호</li>
</ul>
<hr>
<h2 id="인바운드와-아웃바운드">인바운드와 아웃바운드</h2>
<ul>
<li>*<em>인바운드 (Inbound) *</em>: 외부 → 내부로 들어오는 요청 (예: 외부에서 내 서버 접속)</li>
<li>*<em>아웃바운드 (Outbound) *</em>: 내부 → 외부로 나가는 요청 (예: 내가 인터넷에 접속)</li>
</ul>
<hr>
<h2 id="nat">NAT</h2>
<p>NAT (Network Address Translation) 는 네트워크 주소 변환 기술입니다.
내부 사설 아이피 주소를 공인 아이피 주소로 변환하거나 그 반대로 바꾸는 기능입니다.
여러 대의 내부 장치가 하나의 공인 아이피를 통해 인터넷과 통신하고, IPv4 의 주소 부족 문제 해결에 도움을 줍니다.</p>
<hr>
<h2 id="포트-포워딩">포트 포워딩</h2>
<p>포트 포워딩 (Port Forwarding) 은 외부에서 내부 네트워크의 특정 장치 (서버) 로 접근할 수 있도록 포트를 연결해주는 설정입니다.</p>
<ol>
<li>내 컴퓨터에서 내부 서버가 <code>192.168.0.10:8080</code> 에서 실행 중 이다.</li>
<li>외부 사용자가 <code>공인IP:80</code> 으로 접근</li>
<li>포트 포워딩 설정을 통해 외부 사용자가 <code>공인IP:80</code> 를 통해 <code>192.168.0.10:8080</code> 에 접근할 수 있게 한다.</li>
</ol>
<p>-&gt; 라우터가 요청을 내부 서버로 포워딩 해주는 구조</p>
<hr>
<h2 id="dns-서버란">DNS 서버란?</h2>
<p>DNS (Domain Name System) 는 <strong>도메인 이름을 IP 주소로 변환</strong>해주는 시스템입니다.</p>
<ul>
<li>사용자는 <code>naver.com</code> 처럼 도메인을 기억하지만, 실제 네트워크 통신은 <code>223.130.200.104</code> 같은 IP 주소로 이루어집니다.</li>
<li>DNS는 이런 매핑 작업을 처리해줍니다.</li>
</ul>
<p>도커 네트워크에서도 <strong>컨테이너 이름이 곧 도메인 역할</strong>을 하며, 내부 DNS 기능을 통해 서로 통신합니다.</p>
<hr>
<p>지금까지 도커를 잘 이해하기 위해서 네트워크에 대해서 알아봤습니다. 지금부터는 도커의 가상 네트워크에 대해서 알아보겠습니다.</p>
<h1 id="도커를-공부하는-개발자가-반드시-알아야-할-도커-상식">도커를 공부하는 개발자가 반드시 알아야 할 도커 상식</h1>
<p>네트워크는 크게 공인망과 사설망으로 나뉩니다.</p>
<h3 id="공인망">공인망</h3>
<p>: <strong>외부 네트워크</strong>를 사용한 통신망</p>
<ul>
<li><p>외부에서 접근가능한 네트워크</p>
<h3 id="사설망">사설망</h3>
<p>: <strong>내부 네트워크</strong>를 사용한 통신망</p>
</li>
<li><p>내부에서만 통신이 가능한 네트워크</p>
</li>
<li><p>라우터는 내부 서버들에게 사설아이피를 할당합니다.</p>
<h3 id="라우터">라우터</h3>
<p>는 아까 말했듯이 <strong>공인 아이피 ↔ 사설 아이피 간의 중계 역할</strong>을 합니다.</p>
</li>
<li><p>외부에서 들어오는 요청은 공인 아이피를 통해 라우터에 도달하고, 라우터는 이를 내부 서버 (사설아이피) 로 전달합니다. <code>(외부 요청 --공인아이피--&gt; 라우터 -&gt; 내부서버)</code></p>
</li>
<li><p>반대로, 내부 서버에서 외부로 나갈 때도 라우터가 NAT 기능을 통해 사설 아
이피로 변환해줍니다. <code>(내부서버 (사설아이피) --NAT--&gt; 공인아이피)</code></p>
</li>
</ul>
<hr>
<p>라우터를 통해 내부 서버들은 사설 IP를 할당받고, 이러한 사설망 내의 서버 위에서 도커가 실행됩니다.
도커는 해당 서버 안에서 <strong>자체적으로 가상 네트워크 환경을 구축</strong>하여 컨테이너 간의 통신을 관리합니다.
지금부터 <strong>도커의 가상 네트워크</strong>에서 사용되는 <strong>여러가지 용어와 개념</strong>들에 대해서 정리하고 알아보겠습니다.</p>
<hr>
<h2 id="도커-가상-네트워크란">도커 가상 네트워크란?</h2>
<p>도커의 가상 네트워크에서는 크게 브릿지 네트워크로 나뉘고 그 안에서 각각의 컨테이너들이 동작합니다.</p>
<p>도커 명령어를 통해 가상의 네트워크 브릿지를 생성할 수 있습니다.</p>
<hr>
<h3 id="도커-네트워크-관련-명령어">도커 네트워크 관련 명령어</h3>
<p>네트워크 리스트 조회 (현재 어떤 브릿지들이 생성되어 있는지)</p>
<pre><code>docker network ls</code></pre><p><img src="https://velog.velcdn.com/images/developer_gaeun/post/ecd45331-66c6-4f5d-8963-3bcf36b2f45e/image.png" alt="">
이렇게 뜨는것이 기본적인 상태 입니다.</p>
<p>네트워크 상세 정보 조회</p>
<pre><code>docker network inspect {네트워크명}</code></pre><p><img src="https://velog.velcdn.com/images/developer_gaeun/post/afddc21d-ec93-4948-8899-9ab72009c2d1/image.png" alt="">
해당 명령어를 실행하면 사진과 같이 많은 정보들이 나옵니다.
그중에서 우리가 봐야할 것은 
<img src="https://velog.velcdn.com/images/developer_gaeun/post/23509a63-2d5c-41cf-8226-a1a921f77bf9/image.png" alt="">
바로 이 부분입니다.
Subnet 다음에 오는 주소는 공인아이피로 부터 할당받은 사설아이피고 밑에 Gateway 다음에 오는 주소는 사이더를 이용하여 내부 서버에 할당받은 아이피 주소 입니다.
Subnet 옆에 오는 주소는 사이더 표기법이고, prefix 가 16이므로 <code>172.17.255.255</code> 까지 이용가능함을 알 수 있습니다.</p>
<p>네트워크 생성</p>
<pre><code>docker network create {네트워크명}</code></pre><p>네트워크 삭제</p>
<pre><code>docker network rm {네트워크명}</code></pre><p>위와 같은 두 명령어를 통해 네트워크를 생성하고 삭제할 수 있습니다.</p>
<hr>
<p>지금부터는 오늘 배운 개념들에 대해서 실습을 통해 알아보겠습니다.
각각 다른 브릿지에 있는 두 컨테이너가 서로 접속 가능한지 알아보겠습니다.</p>
<h3 id="실습과정">실습과정</h3>
<ol>
<li><code>second-bridge</code> 라는 이름의 브릿지를 한개 더 만들어보겠습니다.<pre><code>docker network create second-bridge
</code></pre></li>
</ol>
<pre><code>![](https://velog.velcdn.com/images/developer_gaeun/post/7fbac433-6e66-44f6-9e5f-833e66d6abf7/image.png)
2. `docker network ls` 명령어를 통해 확인해보면 사진과 같이 새로운 브릿지 `second-bridge` 가 생성되었음을 확인할 수 있습니다.
3. 지금부터는 `bridge` 에 `ubuntuA`, `ubuntuB`, `second-bridge` 에 `ubuntuC` 를 생성해보겠습니다.</code></pre><p>docker run -d --name ubuntuA devwikirepo/pingbuntu</p>
<pre><code>아무 설정없이 컨테이너를 생성하면 기본 브릿지에서 생성됩니다.

macOS 라면 cmd + tab 을 눌러 화면을 한개 더 띄우고 아래의 명령어를 실행시켜 보겠습니다.</code></pre><p>docker run -it --name ubuntuB devwikirepo/pingbuntu bin/bash</p>
<pre><code>ubuntuA 와 달리 -it 로 실행시키면 컨테이너 내부에 접근할 수 있습니다.
한번 더 cmd + tab 을 눌러 화면을 한개 더 띄우고 아래의 명령어를 실행시켜 보겠습니다.</code></pre><p>docker run -it --network second-bridge --name ubuntuC devwikirepo/pingbuntu bin/bash</p>
<p>```</p>
<p>명령어를 이용하여 <code>ubuntuA</code>, <code>ubuntuB</code> 는 <code>bridge</code> 에 <code>ubuntuC</code> 는 <code>second-bridge</code> 에 생성했습니다.
정말 그럴까요? <code>docker inspect {컨테이너 명}</code> 을 이용하여 확인해보겠습니다.
<code>ubuntuA</code> 를 생성한 쉘에서 확인하겠습니다.</p>
<p><code>docker inspect ubuntuA</code>, <code>docker inspect ubuntuB</code> 의 실행결과
<img src="https://velog.velcdn.com/images/developer_gaeun/post/5a7cb9a7-148d-4e4e-9e96-1c025ddd3275/image.png" alt=""></p>
<p><code>docker inspect ubuntuC</code> 의 실행결과
<img src="https://velog.velcdn.com/images/developer_gaeun/post/1c14bd34-3fd3-4617-a208-387ac9807e37/image.png" alt=""></p>
<p>각 컨테이너가 저희의 예상과 같이 실행되고 있음을 알 수 있습니다.</p>
<p>이제 컨테이너 끼리 서로 접속해보겠습니다. 이때 <code>ping {IP}</code> 을 사용합니다.
위에 inspect 명령어를 사용했을때 <code>IPAddress</code> 옆에 있는 아이피주소가 그 컨테이너가 할당받은 아이피 입니다.</p>
<p><code>ubuntuB</code> 가 실행중인 쉘에서 <code>ping {ubuntuA IP}</code> 를 실행하면 요청이 정상적으로 작동합니다.</p>
<p><code>ping {ubuntuC IP}</code> 를 실행하면 요청이 가지 않습니다. 즉 두개의 다른 브릿지에 있는 컨테이너는 통신할 수 없음을 알 수 있습니다.</p>
<p><code>ubuntuC</code> 가 실행중인 쉘에서 <code>ping {ubuntuA or B IP}</code> 를 실행해도 통신할 수 없습니다.</p>
]]></description>
        </item>
    </channel>
</rss>