<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Bien.log</title>
        <link>https://velog.io/</link>
        <description>🙀</description>
        <lastBuildDate>Sun, 28 Sep 2025 15:23:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Bien.log</title>
            <url>https://images.velog.io/images/2-seulgi/profile/b3da4bd9-375d-47ba-ac4e-b3ab9328f802/591d3eb05b05a24cf0f4c3e794f5448d.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Bien.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/2-seulgi" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[나만 모르는 Mock과 단위 테스트 정리]]></title>
            <link>https://velog.io/@2-seulgi/%EB%82%98%EB%A7%8C-%EB%AA%A8%EB%A5%B4%EB%8A%94-Mock%EA%B3%BC-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@2-seulgi/%EB%82%98%EB%A7%8C-%EB%AA%A8%EB%A5%B4%EB%8A%94-Mock%EA%B3%BC-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 28 Sep 2025 15:23:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>면접에서 테스트에 대해 <del>진득하게 털리고</del> 차근차근 공부하는 테스트🥲 다음엔 털리지 말자</p>
</blockquote>
<h2 id="1mock이란-무엇인가">1.Mock이란 무엇인가?</h2>
<h3 id="mock의-정의">Mock의 정의</h3>
<ul>
<li><strong>Mock</strong>: 실제 객체를 대신하는 &quot;가짜 객체&quot;</li>
<li>실제 외부 의존성(DB, API 등) 없이 테스트할 수 있게 해주는 도구</li>
<li>테스트에서 &quot;외부 협력자&quot;의 동작을 미리 정의해서 사용</li>
</ul>
<h3 id="기본-코드-예시">기본 코드 예시</h3>
<pre><code class="language-java">@ExtendWith(MockitoExtension.class)
public class PointServiceTest {
    @InjectMocks PointService pointService; // Mock을 주입받은 테스트 대상
    @Mock PointAccountRepository pointAccountRepository; // 가짜 Repository

    @Test
    void 신규계정_잔액은_0원(){
        //given 
        String userId = &quot;user1&quot;;
        given(pointAccountRepository.save(any()))
                .willAnswer(invocation -&gt; invocation.getArgument(0)); //&quot;save() 메서드가 호출되면, 첫 번째 인자(0번 인덱스)를 그대로 반환해줘&quot;

        //when
        var account = pointService.createAccount(userId);

        //then
        assertThat(account.getBalance()).isZero();
    }
}</code></pre>
<h2 id="2mock을-사용하는-이유">2.Mock을 사용하는 이유</h2>
<h3 id="실제-객체-사용-시-문제점">실제 객체 사용 시 문제점</h3>
<pre><code class="language-java">// Mock 없는 테스트 
@Test
void 실제객체_사용() {
    // 실제 DB 연결이 필요함
    PointAccountRepository realRepository = new JpaPointAccountRepository();
    PointService pointService = new PointService(realRepository);

    var acc = pointService.createAccount(&quot;user1&quot;); // 실제 DB 접근
}</code></pre>
<p>발생하는 문제들:</p>
<ul>
<li>속도 저하 : DB연결, 쿼리 실행 시간 소요</li>
<li>환경 의존성 : DB서버가 켜져있어야 함</li>
<li>테스트 간섭: 다른 테스트의 DB 상태에 영향받음</li>
<li>복잡한 설정: 테스트용 DB 환경 구축 필요</li>
<li>불안정성: 네트워크 장애 시 테스트 실패<h3 id="mock-사용-시-장점">Mock 사용 시 장점</h3>
</li>
<li>빠른 속도: 메모리에서만 동작 (1-2ms)</li>
<li>독립성: 다른 테스트나 외부 환경과 무관</li>
<li>안정성: DB나 네트워크 장애와 무관</li>
<li>단순함: 복잡한 외부 설정 불필요</li>
<li>집중: 테스트하고 싶은 로직에만 집중 가능</li>
</ul>
<h2 id="3-mock-동작-원리-이해">3. Mock 동작 원리 이해</h2>
<h3 id="객체-생성-vs-메서드-호출">객체 생성 vs 메서드 호출</h3>
<p>내가 계속 혼동하는 부분:</p>
<pre><code class="language-java">// 1. 객체 생성 - DB 연결 하지 않음
PointAccountRepository realRepository = new JpaPointAccountRepository();
// ↑ 이 시점에는 DB 연결 없음. 그냥 객체만 생성

// 2. 메서드 호출 - 여기서 DB 연결!
realRepository.save(account); 
// ↑ 이 시점에서 실제 DB에 INSERT 쿼리 실행</code></pre>
<h3 id="mock-vs-real-동작-비교">Mock vs Real 동작 비교</h3>
<pre><code>// Real Repository 동작
pointService.createAccount(&quot;user1&quot;) {
    1. new PointAccount(&quot;user1&quot;) 생성
    2. repository.save() 호출
       → DB 연결
       → INSERT 쿼리 실행  
       → 네트워크 통신
       → 트랜잭션 처리
       → 결과 반환 (느림, 복잡함)
}

// Mock Repository 동작  
pointService.createAccount(&quot;user1&quot;) {

    // 1단계: PointAccount 객체 생성
    PointAccount account = new PointAccount(&quot;user1&quot;, 0L);
    // 메모리에 실제 객체 생성됨
    // account = PointAccount { userId: &quot;user1&quot;, balance: 0 }

    // 2단계: repository.save() 호출
    return pointAccountRepository.save(account);
    //                            ↑
    //                    이 account 객체를 인자로 넘김

    // 3단계: Mock이 willAnswer 규칙 적용
    // invocation.getArgument(0) = 넘어온 첫 번째 인자 = account 객체
    // 즉, 넘어온 account 객체를 그대로 반환
}</code></pre><h2 id="4tdd에서-mock-사용법">4.TDD에서 Mock 사용법</h2>
<h3 id="tdd의-올바른-순서">TDD의 올바른 순서</h3>
<p>*<em>🔴 Red → 🟢 Green → 🔵 Blue(Refactor)
*</em>
Step 1: Red - 실패하는 테스트 작성</p>
<pre><code class="language-java">@Test
void 신규계정_잔액은_0원(){
    //given
    String userId = &quot;user1&quot;;

    //when
    var account = pointService.createAccount(userId); // 컴파일 에러!

    //then
    assertThat(account.getBalance()).isZero();
}</code></pre>
<p>결과: PointService.createAccount() 메서드가 없거나 null을 반환해서 에러를 만든다. 지금 경우는 그냥 없음.</p>
<p>Step 2: Green - 최소한의 코드로 통과</p>
<pre><code class="language-java">public class PointService {
    public PointAccount createAccount(String userId) {
        return new PointAccount(userId, 0L); // 하드코딩으로 통과
    }
}</code></pre>
<p>결과: 테스트 통과! (하지만 하드코딩)</p>
<p>Step 3: Blue - 리팩토링 (Mock 추가)</p>
<pre><code class="language-java">public class PointService {
    private final PointAccountRepository repository;

    public PointAccount createAccount(String userId) {
        PointAccount account = new PointAccount(userId, 0L);
        return repository.save(account); // Repository 패턴 적용
    }
}

// 테스트에 Mock 추가
@Test
void 신규계정_잔액은_0원(){
    //given
    String userId = &quot;user1&quot;;
    given(pointAccountRepository.save(any()))
        .willAnswer(invocation -&gt; invocation.getArgument(0));

    //when
    var account = pointService.createAccount(userId);

    //then
    assertThat(account.getBalance()).isZero();
    assertThat(account.getUserId()).isEqualTo(userId);
}</code></pre>
<p>Step 3: Blue - 리팩토링 (Mock 추가)</p>
<pre><code>public class PointService {
    private final PointAccountRepository repository;

    public PointAccount createAccount(String userId) {
        PointAccount account = new PointAccount(userId, 0L);
        return repository.save(account); // Repository 패턴 적용
    }
}

// 테스트에 Mock 추가
public class PointService {
    private final PointAccountRepository repository;

    public PointAccount createAccount(String userId) {
        PointAccount account = new PointAccount(userId, 0L);
        return repository.save(account); // Repository 패턴 적용
    }
}

// 테스트에 Mock 추가
@Test
void 신규계정_잔액은_0원(){
    //given
    String userId = &quot;user1&quot;;
    given(pointAccountRepository.save(any()))
        .willAnswer(invocation -&gt; invocation.getArgument(0));

    //when
    var account = pointService.createAccount(userId);

    //then
    assertThat(account.getBalance()).isZero();
    assertThat(account.getUserId()).isEqualTo(userId);
}</code></pre><h2 id="5mock-설정이-없으면-어떻게-될까">5.Mock 설정이 없으면 어떻게 될까?</h2>
<h3 id="잘못된-예시">잘못된 예시</h3>
<pre><code class="language-java">@Test
void Mock설정_없는_잘못된_테스트(){
    //given
    String userId = &quot;user1&quot;;
    // ← Mock 설정이 없음!

    //when
    var account = pointService.createAccount(userId); 
    // ↑ pointAccountRepository.save()가 null 반환!

    //then
    assertThat(account.getBalance()).isZero(); // NullPointerException 발생!
}</code></pre>
<h2 id="6-given-when-then-구조에서-mock의-역할">6. Given-When-Then 구조에서 Mock의 역할</h2>
<h3 id="given-부분에서-mock-설정의-의미">Given 부분에서 Mock 설정의 의미</h3>
<pre><code class="language-java">//given
String userId = &quot;user1&quot;; // ← 실제 테스트 데이터 준비
given(pointAccountRepository.save(any())) // ← Mock 동작 정의 (인프라 준비)
        .willAnswer(invocation -&gt; invocation.getArgument(0));</code></pre>
<p>구분해서 이해해보자:</p>
<ul>
<li>테스트 데이터: String userId = &quot;user1&quot;</li>
<li>Mock 설정: given().willAnswer() - 외부 의존성의 가짜 동작 정의</li>
</ul>
<h3 id="더-명확한-분리-방법">더 명확한 분리 방법</h3>
<pre><code class="language-java">@BeforeEach
void setUp() {
    // Mock 동작을 여기서 미리 설정 (인프라 준비)
    given(pointAccountRepository.save(any(PointAccount.class)))
            .willAnswer(invocation -&gt; invocation.getArgument(0));
}

@Test
void 신규계정_잔액은_0원(){
    //given - 순수한 테스트 데이터만
    String userId = &quot;user1&quot;;

    //when
    var account = pointService.createAccount(userId);

    //then
    assertThat(account.getBalance()).isZero();
    assertThat(account.getUserId()).isEqualTo(userId);

    // 추가 검증: repository의 save가 실제로 호출되었는지
    verify(pointAccountRepository).save(any(PointAccount.class));
}</code></pre>
<h2 id="7핵심-개념-정리">7.핵심 개념 정리</h2>
<h3 id="관심사의-분리">관심사의 분리</h3>
<pre><code class="language-java">@Test 
void 신규계정_잔액은_0원(){
    // 우리의 관심사: &quot;PointService가 새 계정을 올바르게 생성하는가?&quot;
    // 관심 없는 것: &quot;Repository가 DB에 잘 저장하는가?&quot; 
    // ↑ 이건 Repository 테스트에서 별도로 검증!

    var acc = pointService.createAccount(&quot;user1&quot;);
    assertThat(acc.getBalance()).isZero(); // 이것만 검증하고 싶음
}</code></pre>
<h3 id="mock의-핵심-철학">Mock의 핵심 철학</h3>
<blockquote>
<p>내가 테스트하고 싶은 로직에만 집중할 수 있게 해준다</p>
</blockquote>
<ul>
<li>PointService의 로직 테스트 ← 여기에 집중</li>
<li>Repository의 DB 저장 로직 ← 별도 테스트에서 검증</li>
</ul>
<h2 id="8단위-테스트-철학-논쟁-detroit-vs-london">8.단위 테스트 철학 논쟁: Detroit vs London</h2>
<blockquote>
<p>이게 면접에서 나왔고 테스트는 mock을 사용한 테스트 밖에 모른다고 멍청한 대답을 당당히 하고 나왔다...🤦🏻‍♀️</p>
</blockquote>
<h3 id="두-파벌의-핵심-주장">두 파벌의 핵심 주장</h3>
<p>*<em>🏭 Detroit School (Classicist) - &quot;Mock은 가짜 테스트다!&quot;
*</em>
핵심 철학: &quot;실제 객체들이 상호작용해야 의미 있는 테스트&quot;
주요 주장:</p>
<ul>
<li><p>Mock으로 하는 테스트는 진짜 테스트가 아니다</p>
</li>
<li><p>실제 객체들의 협력을 검증해야 한다</p>
</li>
<li><p>Mock 설정이 틀리면 거짓 안전감만 준다</p>
</li>
<li><p>리팩토링에 강하다 (내부 구현 변경에 덜 민감)</p>
<pre><code class="language-java">// Detroit School 방식
@Test
void detroit_방식() {
  Calculator realCalc = new Calculator();        // 실제 객체
  TaxService realTax = new TaxService();         // 실제 객체
  OrderService order = new OrderService(realCalc, realTax);

  // 실제 메서드들이 실제로 호출되고 계산됨
  assertEquals(110, order.calculateTotal(100));
}</code></pre>
</li>
<li><p>*🏢 London School (Mockist) - &quot;Mock이 진짜 단위테스트다!&quot;</p>
</li>
<li><p>*
핵심 철학: &quot;의존성 있으면 단위테스트가 아니다&quot;
주요 주장:</p>
</li>
<li><p>격리가 완벽해야 진짜 단위테스트다</p>
</li>
<li><p>협력 객체 문제 때문에 내 테스트가 깨지면 안 된다</p>
</li>
<li><p>하나의 클래스만 테스트해야 한다</p>
</li>
<li><p>빠르고 독립적이어야 한다</p>
<pre><code class="language-java">// London School 방식
@Test  
void london_방식() {
  Calculator mockCalc = mock(Calculator.class);
  TaxService mockTax = mock(TaxService.class);
  when(mockCalc.calculate(100)).thenReturn(100);
  when(mockTax.getTax(100)).thenReturn(10);

  OrderService order = new OrderService(mockCalc, mockTax);
  assertEquals(110, order.calculateTotal(100));  // 완벽히 격리된 테스트
}</code></pre>
<h3 id="각-방식의-장단점-비교">각 방식의 장단점 비교</h3>
</li>
<li><p><em>Detroit School (실제 객체)*</em></p>
</li>
<li><p><em>장점:*</em></p>
</li>
<li><p>현실성: 실제 상호작용을 검증하므로 더 신뢰할 수 있음</p>
</li>
<li><p>리팩토링 안전: 내부 구현 바뀌어도 테스트가 깨지지 않음</p>
</li>
<li><p>간단함: Mock 설정 코드 없이 깔끔</p>
</li>
<li><p>통합 검증: 여러 객체 간 협력도 함께 검증</p>
</li>
</ul>
<p><strong>단점:</strong></p>
<ul>
<li>속도: 실제 계산/로직 수행하니까 상대적으로 느림</li>
<li>복잡한 설정: 실제 객체들의 의존성도 다 준비해야 함</li>
<li>테스트 범위 애매: 여러 클래스 함께 테스트하니까 단위테스트인지 애매</li>
<li>디버깅 어려움: 어디서 실패했는지 파악하기 어려울 수 있음</li>
</ul>
<p><strong>London School (Mock)</strong>
<strong>장점:</strong></p>
<ul>
<li>빠름: 실제 로직 수행 안 하니까 매우 빠름</li>
<li>격리: 테스트 대상 클래스만 집중해서 테스트</li>
<li>제어 가능: 원하는 시나리오 쉽게 만들 수 있음 (예외 상황 등)</li>
<li>명확한 범위: 진짜 &quot;단위&quot;테스트</li>
</ul>
<p><strong>단점:</strong></p>
<ul>
<li>가짜 안전감: Mock이 잘못되면 실제로는 안 되는데 테스트는 통과</li>
<li>구현 의존적: 내부 구현 바뀌면 Mock 설정도 다 바껴야 함</li>
<li>복잡한 Mock 설정: when().thenReturn() 코드가 많아질 수 있음</li>
<li>Mock 동기화: 실제 구현과 Mock 동작이 달라질 위험</li>
</ul>
<h2 id="9-실무에서-언제-어떤-방식을-쓸까">9. 실무에서 언제 어떤 방식을 쓸까?</h2>
<h3 id="mock을-써야-하는-경우">Mock을 써야 하는 경우</h3>
<ol>
<li><p>외부 시스템 의존성</p>
<pre><code class="language-java">void 이메일발송_테스트(){
 // 실제 이메일 서버에 보낼 수 없으니까 Mock 필수
 EmailService mockEmail = mock(EmailService.class);
 UserService service = new UserService(mockEmail);

 service.registerUser(&quot;test@test.com&quot;);

 verify(mockEmail).send(&quot;test@test.com&quot;, &quot;Welcome!&quot;);    
}</code></pre>
</li>
<li><p>느린 작업 (DB, 네트워크, 파일 I/O)</p>
<pre><code class="language-java">@Test
void 데이터베이스_저장_테스트() {
 // 실제 DB 연결하면 너무 느려서 Mock 사용
 UserRepository mockRepo = mock(UserRepository.class);
 UserService service = new UserService(mockRepo);

 service.saveUser(new User(&quot;test&quot;));

 verify(mockRepo).save(any(User.class));
}</code></pre>
</li>
<li><p>예외 상황 테스트</p>
<pre><code class="language-java">@Test
void 네트워크_장애_테스트() {
 // 실제로 네트워크 장애 만들기 어려우니까 Mock으로 시뮬레이션
 ApiClient mockApi = mock(ApiClient.class);
 when(mockApi.call()).thenThrow(new NetworkException());

 PaymentService service = new PaymentService(mockApi);

 assertThrows(PaymentFailedException.class, 
     () -&gt; service.processPayment(100));
}</code></pre>
</li>
</ol>
<h3 id="실제-객체를-써야-하는-경우">실제 객체를 써야 하는 경우</h3>
<ol>
<li><p>값 객체 (Value Object)</p>
<pre><code class="language-java">@Test
void 금액_계산_테스트() {
 // Money, Price 같은 값 객체는 실제 객체 사용
 Money price = new Money(1000);
 Money tax = new Money(100);

 Money total = price.add(tax);

 assertThat(total.getAmount()).isEqualTo(1100);
}</code></pre>
</li>
<li><p>단순한 계산 로직</p>
<pre><code class="language-java">@Test
void 계산기_테스트() {
 // 간단한 계산기는 실제 객체가 더 의미있음
 Calculator calc = new Calculator();
 MathService service = new MathService(calc);

 int result = service.calculate(10, 5);

 assertThat(result).isEqualTo(15);
}</code></pre>
</li>
<li><p>도메인 로직의 협력</p>
<pre><code class="language-java">@Test
void 주문_비즈니스_로직_테스트() {
 // 핵심 도메인 로직은 실제 객체들의 협력이 중요
 PriceCalculator priceCalc = new PriceCalculator();
 DiscountPolicy discountPolicy = new VipDiscountPolicy();
 OrderService service = new OrderService(priceCalc, discountPolicy);

 Order order = service.createOrder(&quot;VIP고객&quot;, 10000);

 assertThat(order.getFinalPrice()).isEqualTo(9000); // 10% 할인
}</code></pre>
<blockquote>
<p>결론: 적절히 같이 쓰자 🫶🏻</p>
</blockquote>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA N+1, 왜 생기고 어떻게 줄이나?]]></title>
            <link>https://velog.io/@2-seulgi/%EB%B0%B1%EC%97%94%EB%93%9C-%ED%92%88%EC%A7%88%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EB%AF%BC%ED%95%9C-%EB%82%B4%EC%97%AD-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@2-seulgi/%EB%B0%B1%EC%97%94%EB%93%9C-%ED%92%88%EC%A7%88%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EB%AF%BC%ED%95%9C-%EB%82%B4%EC%97%AD-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Sun, 21 Sep 2025 15:45:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>실제 실무 도입은 아니고, 혼자 프로젝트를 만들어보면서 고민했던 포인트를 정리함. <del>언젠간 실무적으로도 깊게 고민할 날이 오길</del></p>
</blockquote>
<h2 id="시리즈가안-계속-손볼-예정">시리즈(가안, 계속 손볼 예정)</h2>
<ol>
<li>JPA N+1, 왜 생기고 어떻게 줄이나? ← (이번 글)</li>
<li>글로벌 예외 처리: 에러 모델·로그·관측성</li>
<li>통합검색 설계: 질의/색인/랭킹/페이징</li>
<li>테스트 준비 vs 테스트 헬퍼: setUp · 팩토리 · 빌더 · Reflection</li>
<li>단위 테스트의 범위와 경계: 무엇을 테스트할까</li>
<li>프론트 친화적 API 스펙 &amp; Swagger 문서 전략</li>
</ol>
<h2 id="jpa-n1-왜-생기고-어떻게-줄이나">JPA N+1, 왜 생기고 어떻게 줄이나?</h2>
<h3 id="내가-이해한-핵심">내가 이해한 핵심</h3>
<ul>
<li>지연 로딩(LAZY) + 컬렉션(=to-many) 접근 타이밍이 겹치면 <strong>“쿼리 폭증(N+1)”</strong>이 터진다.</li>
<li>해결의 핵심은 쿼리 수를 내가 통제하는 것:
fetch join, 두 단계 로딩(IDs → fetch join), DTO 프로젝션(flat), @BatchSize, (필요하면 캐시/리드모델).</li>
</ul>
<h3 id="1-문제-상황">1. 문제 상황</h3>
<p>도메인 (다대다를 중간 엔티티로 표현)</p>
<pre><code class="language-java">@Entity
class Post {
    @Id @GeneratedValue Long id;
    String title;
    String content;

    // Post ⟷ PostTag(다대일)
    @OneToMany(mappedBy = &quot;post&quot;, fetch = FetchType.LAZY)
    Set&lt;PostTag&gt; tags = new HashSet&lt;&gt;();
}

@Entity
class PostTag {
    @Id @GeneratedValue Long id;

    @ManyToOne(fetch = FetchType.LAZY) Post post;
    @ManyToOne(fetch = FetchType.LAZY) Tag tag;
}

@Entity
class Tag {
    @Id @GeneratedValue Long id;
    String name;
}
</code></pre>
<p>검색 레포지토리 (제목, 태그명으로 검색, 페이징)</p>
<pre><code class="language-java">public interface PostRepository extends JpaRepository&lt;Post, Long&gt; {

    @Query(value = &quot;&quot;&quot;
        SELECT DISTINCT p
        FROM Post p
        LEFT JOIN p.tags pt
        LEFT JOIN pt.tag t
        WHERE
            LOWER(function(&#39;REPLACE&#39;, p.title,  &#39; &#39;, &#39;&#39;)) LIKE LOWER(CONCAT(&#39;%&#39;, function(&#39;REPLACE&#39;, :keyword, &#39; &#39;, &#39;&#39;), &#39;%&#39;))
         OR LOWER(t.name) LIKE LOWER(CONCAT(&#39;%&#39;, :keyword, &#39;%&#39;))
        &quot;&quot;&quot;,
        countQuery = &quot;&quot;&quot;
        SELECT COUNT(DISTINCT p.id)
        FROM Post p
        LEFT JOIN p.tags pt
        LEFT JOIN pt.tag t
        WHERE
            LOWER(function(&#39;REPLACE&#39;, p.title,  &#39; &#39;, &#39;&#39;)) LIKE LOWER(CONCAT(&#39;%&#39;, function(&#39;REPLACE&#39;, :keyword, &#39; &#39;, &#39;&#39;), &#39;%&#39;))
         OR LOWER(t.name) LIKE LOWER(CONCAT(&#39;%&#39;, :keyword, &#39;%&#39;))
        &quot;&quot;&quot;)
    Page&lt;Post&gt; searchAll(@Param(&quot;keyword&quot;) String keyword, Pageable pageable);
}
</code></pre>
<p>*<em>어디서 N+1이 터졌나?
*</em></p>
<pre><code>// (가벼운 예시) DTO 변환 과정
PostResponse from(Post p) {
    // 여기서 p.getTags()를 건드리는 순간, Post마다 추가 SELECT 발생 → N+1
    List&lt;String&gt; tagNames = p.getTags().stream()
        .map(pt -&gt; pt.getTag().getName()) // 여기도 LAZY 접근
        .toList();
    ...
}
</code></pre><ul>
<li>1번(검색) + 포스트 수만큼 N번(태그 로딩) (+ 태그 N번) → N+1</li>
<li>참고: DISTINCT는 중복 제거 용도일 뿐, 연관 컬렉션 로딩 방식을 바꾸지 않는다.</li>
</ul>
<h3 id="2-왜-생기나">2. 왜 생기나?</h3>
<ul>
<li>지연 로딩(LAZY) = “필요해질 때 그때 가서 DB에서 가져오기”.</li>
<li>컨트롤러에서 엔티티를 JSON으로 직렬화하거나, DTO로 만들려고 게터를 호출하는 순간
→ “어? 필요하네?” 하고 추가 SELECT가 튀어나온다.</li>
<li>특히 컬렉션(to-many) 은 목록 화면에서 루프 돌리며 자주 건드리니까 더 잘 터진다.<blockquote>
<p><strong>JOIN vs FETCH JOIN</strong> 
LEFT JOIN : 필터/조건을 위한 조인(로딩 시점은 그대로 LAZY)
LEFT JOIN FETCH : “이번 쿼리에 같이 가져와” (해당 연관을 즉시 채움)</p>
</blockquote>
</li>
</ul>
<h3 id="3-그래서-어떻게-줄이나">3. 그래서 어떻게 줄이나?</h3>
<p>*<em>3-1. 먼저, 쿼리 수를 눈으로 본다
*</em></p>
<ul>
<li>왜? 나도 처음에 눈으로 보기전에는 몰랐듯, N+1은 느낌으로 오지 않았다. 따라서 직접 눈으로 확인 할 수 있도록 사전 작업을 해두면 된다.하나의 API(혹은 서비스 메서드) 호출시 SELECT 가 몇번 나가는가? 이걸 확인하자<pre><code>// 개발용 로그 보기
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=debug</code></pre></li>
</ul>
<pre><code class="language-java">// datasource-proxy로 테스트에 “쿼리 수 단언”
// 테스트에서
QueryCountHolder.clear();
service.searchAll(&quot;java&quot;, PageRequest.of(0, 10));
int selects = QueryCountHolder.getGrandTotal().getSelect();
assertThat(selects).isLessThanOrEqualTo(3); // 상한 고정
</code></pre>
<blockquote>
<p>운영에서는 Actuator + Micrometer 를 사용해서 확인 하는 방법을 사용하는거 같은데, 일단 테스트 상한부터 걸어두면 체감이 온다. <del>이건 너무 어렵다... 추가적인 공부가 필요할듯🥲</del></p>
</blockquote>
<p>*<em>3-2. 컬렉션을 화면에 뿌릴 땐: 목적에 맞는 방법을 고른다
*</em>
*<em>옵션 A) 두 단계 로딩 (IDs → FETCH JOIN) — 페이징 + 자식 필요할 때 베스트
*</em></p>
<ol>
<li>ID만 페이징해서 뽑고</li>
<li>그 ID들만 대상으로 연관을 한 번에 적재(FETCH JOIN)<pre><code>// 1) 부모 ID만 페이징(검색/필터는 여기에)
@Query(value = &quot;&quot;&quot;
 SELECT DISTINCT p.id
 FROM Post p
 LEFT JOIN p.tags pt
 LEFT JOIN pt.tag t
 WHERE
     LOWER(function(&#39;REPLACE&#39;, p.title,  &#39; &#39;, &#39;&#39;)) LIKE LOWER(CONCAT(&#39;%&#39;, function(&#39;REPLACE&#39;, :kw, &#39; &#39;, &#39;&#39;), &#39;%&#39;))
  OR LOWER(t.name) LIKE LOWER(CONCAT(&#39;%&#39;, :kw, &#39;%&#39;))
 &quot;&quot;&quot;,
 countQuery = &quot;&quot;&quot;
 SELECT COUNT(DISTINCT p.id)
 FROM Post p
 LEFT JOIN p.tags pt
 LEFT JOIN pt.tag t
 WHERE
     LOWER(function(&#39;REPLACE&#39;, p.title,  &#39; &#39;, &#39;&#39;)) LIKE LOWER(CONCAT(&#39;%&#39;, function(&#39;REPLACE&#39;, :kw, &#39; &#39;, &#39;&#39;), &#39;%&#39;))
  OR LOWER(t.name) LIKE LOWER(CONCAT(&#39;%&#39;, :kw, &#39;%&#39;))
 &quot;&quot;&quot;)
Page&lt;Long&gt; searchPostIds(@Param(&quot;kw&quot;) String keyword, Pageable pageable);
</code></pre></li>
</ol>
<p>// 2) 그 ID들만 대상으로 연관까지 한 번에
@Query(&quot;&quot;&quot;
    SELECT DISTINCT p
    FROM Post p
    LEFT JOIN FETCH p.tags pt
    LEFT JOIN FETCH pt.tag t
    WHERE p.id IN :ids
    &quot;&quot;&quot;)
List<Post> findWithTagsByIdIn(@Param(&quot;ids&quot;) List<Long> ids);</p>
<pre><code></code></pre><p>// Service: 두 쿼리 오케스트레이션 + 순서 복원 + DTO 변환
Page<Long> idPage = repo.searchPostIds(keyword, pageable);
if (idPage.isEmpty()) return Page.empty(pageable);</p>
<p>List<Post> posts = repo.findWithTagsByIdIn(idPage.getContent());</p>
<p>// IN 절은 순서 보장이 안 됨 → 원래 순서 복원
Map&lt;Long,Integer&gt; order = new HashMap&lt;&gt;();
for (int i = 0; i &lt; idPage.getContent().size(); i++) order.put(idPage.getContent().get(i), i);
posts.sort(Comparator.comparingInt(p -&gt; order.get(p.getId())));</p>
<p>// 이미 컬렉션이 메모리에 채워져 있음 → 추가 SELECT 없이 DTO 변환
List<PostDto> content = posts.stream()
    .map(p -&gt; new PostDto(
        p.getId(),
        p.getTitle(),
        p.getTags().stream().map(pt -&gt; pt.getTag().getName()).distinct().toList()
    ))
    .toList();</p>
<p>return new PageImpl&lt;&gt;(content, pageable, idPage.getTotalElements());</p>
<pre><code>&gt; 결과적으로 
- 예전(N+1): 1번(목록) + 부모 수만큼 N번(자식 로딩) = N+1
- 두 단계: 1번(ID 페이징) + 1번(FETCH JOIN) = 딱 2번

**옵션 B) DTO 프로젝션(Flat) — 조회 전용, 가볍고 빠름 **
- 한 번의 SELECT로 필요한 필드만 가져와서 코드에서 그룹핑.</code></pre><p>public record PostRow(Long postId, String title, Long tagId, String tagName) {}</p>
<p>@Query(&quot;&quot;&quot;
    SELECT new path.to.PostRow(p.id, p.title, t.id, t.name)
    FROM Post p
    LEFT JOIN p.tags pt
    LEFT JOIN pt.tag t
    WHERE
        LOWER(function(&#39;REPLACE&#39;, p.title,  &#39; &#39;, &#39;&#39;)) LIKE LOWER(CONCAT(&#39;%&#39;, function(&#39;REPLACE&#39;, :kw, &#39; &#39;, &#39;&#39;), &#39;%&#39;))
     OR LOWER(t.name) LIKE LOWER(CONCAT(&#39;%&#39;, :kw, &#39;%&#39;))
    &quot;&quot;&quot;)
Page<PostRow> searchAllFlat(@Param(&quot;kw&quot;) String keyword, Pageable pageable);</p>
<pre><code>- **납작(flat)의미:** DB 조인 결과가
post_id | title | tag_id | tag_name 식으로 부모가 행으로 반복되는 모양.화면에서는 postId 기준으로 묶어서 PostDto { id, title, tags:[...] }로 조립.
- **주의(페이징 의미)**
Page&lt;PostRow&gt;의 페이지 크기는 행(row) 기준.
“포스트 10개를 정확히” 보여주고 싶다면 ID 페이지 + flat 조회로 바꿔서,
코드에서 그룹핑 후 응답을 만드는 편이 정확함.


** 옵션 C) @BatchSize — 빠른 완화책(완전 제거 아님)**
- 코드를 크게 바꾸기 어렵고, 임시로라도 쿼리 수를 줄이고 싶을 때.
- LAZY 로딩이 **터지는 “그 순간”**에, 같은 종류의 프록시들을 묶어서 IN (...) 한 방으로 가져와 왕복 횟수를 줄인다.
- N+1을 완전 제거하는 게 아니라  N/size + 1로 줄여 준다.(작은 페이지는 레이어당 보통 1쿼리)</code></pre><p>// (1) 컬렉션 배치: 여러 부모의 &quot;컬렉션 프록시&quot;를 묶어서 초기화
@Entity
class Post {
    @OneToMany(mappedBy = &quot;post&quot;, fetch = FetchType.LAZY)
    @BatchSize(size = 100) // ← post.getTags()를 &#39;처음&#39; 건드릴 때,
    Set<PostTag> tags;     //    세션에 있는 다른 Post들의 tags까지 최대 100개를
}                          //    IN (post_id...) 한 방 SELECT로 채움.</p>
<p>// (2) to-one 타겟 배치: 여러 &quot;엔티티 프록시&quot;를 묶어서 초기화
@Entity
@BatchSize(size = 100)     // ← pt.getTag().getName()을 &#39;처음&#39; 읽을 때,
class Tag {                //    아직 초기화 안 된 Tag 프록시들의 id를 모아
    @Id Long id;           //    IN (tag_id...) 한 방 SELECT로 초기화.
    String name;
}</p>
<pre><code>&gt; “처음 건드리는 순간” Hibernate가 아직 초기화 안 된 동일 종류의 프록시들을 최대 size개까지 모아
… WHERE … IN ( … ) 한 방 SELECT로 초기화한다.
- 컬렉션 프록시 초기화(여러 부모의 컬렉션을 묶음):
SELECT * FROM post_tag WHERE post_id IN ( ... 최대 size개 ... )
- 엔티티 프록시 초기화(여러 to-one 타겟을 묶음):
SELECT * FROM tag WHERE id IN ( ... 최대 size개 ... )
즉, N+1 → N/size + 1 근사로 감소.  

- 장점: 적용 쉬움, 기존 코드 영향 적음
- 단점: 
  - 여전히 여러 번의 SELECT (고QPS/대용량엔 부족)
  - 세션 범위 안에서만 묶임(중간 flush/clear 등 하면 이점 감소).

**3-3. 페이징 + 컬렉션 fetch join은 피한다
**
- 컬렉션(to-many) fetch join을 걸면 행 수가 자식 수만큼 뻥튀기 → DB 페이징 왜곡 or 메모리 페이징(느림).
- 대안은 A(두 단계) 또는 B(Flat).
- 참고: to-one(ManyToOne/OneToOne)은 fetch join + 페이징이 비교적 안전(행 수가 크게 늘지 않음). 하지만 루트에서 컬렉션을 fetch하는 순간 위험해진다.

### 4. 용어를 아주 쉽게 한 번 더
- 쿼리: DB에 “질문 1번”
- N+1: 목록 1번 + 항목마다 추가 질문 N번 ⇒ 총 N+1번
- LAZY(지연 로딩): “필요해질 때 그때 가져오기”
- FETCH JOIN: “이번 질문에 같이 가져와”
- 두 단계 로딩: “먼저 **누구(ID)**인지 고르고 → 그 애들만 한 번에 자세히 가져와”
- Flat(납작): “부모+자식 일부 칼럼을 **행(row)**으로 펼쳐 받기”
- 그룹핑: “같은 postId를 한 덩어리로 합치기”
- @BatchSize: “질문을 묶음으로 줄이기(완전 제거는 아님)”


  &gt; 처음엔 “fetch = LAZY가 성능 최적화라며? 근데 왜 느려지지?” 싶었는데, 결국 **문제는 로딩 방식 자체가 아니라 “언제 어떻게 접근하느냐”**였음.
내가 원하는 화면 모양(부모 10개 + 태그 뱃지들)을 기준으로 쿼리 수를 내가 설계해 버리면,
느림/폭증/페이징 왜곡이 다 설명되고, 해결법도 일관되게 정리됐다. 끝!
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[GitHub Actions 및 AWS EC2를 사용하여 Spring Boot CI/CD를 설정하는 방법]]></title>
            <link>https://velog.io/@2-seulgi/GitHub-Actions-%EB%B0%8F-AWS-EC2%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-Spring-Boot-CICD%EB%A5%BC-%EC%84%A4%EC%A0%95%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@2-seulgi/GitHub-Actions-%EB%B0%8F-AWS-EC2%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-Spring-Boot-CICD%EB%A5%BC-%EC%84%A4%EC%A0%95%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 27 May 2024 08:15:36 GMT</pubDate>
            <description><![CDATA[<h3 id="스프링부트-cicd-세팅하기">스프링부트 CI/CD 세팅하기</h3>
<p>스프링부트 프로젝트를 시작함에 있어서 이번에 꼭 해보려고 한, 말로만 듣던 ci/cd를 gitHub Actions 과 AWS EC2를 사용해서 세팅을 하며 삽질을 한 과정에 대해 글로 남겨두고자 한다. </p>
<p><strong>### CI/CD란?</strong>
<strong>1. 지속적인 통합 (Continuous Integration)</strong>
<strong>- 자동화된 빌드 및 테스트:</strong> 개발시 코드를 저장소에 자주 병합하면, CI 도구가 자동으로 빌드하고 테스트를 실행해주고, 이를 통해 코드를 자주 확인하고, 빌드 실패나 버그를 조기에 발견할 수 있는 장점이 있습니다. 
<strong>- 신속한 피드백:</strong> 개발자들은 코드 변경 사항에 대한 피드백을 빠르게 받을 수 있습니다. 문제가 발생하면 즉시 수정할 수 있어, 개발 속도와 품질이 향상됩니다.
<strong>- 협업 향상:</strong> 여러 개발자가 동시에 작업하는 환경에서 코드 충돌을 줄이고, 원활한 협업을 지원합니다. 코드가 지속적으로 병합되고 테스트되므로, 프로젝트 전체의 코드베이스가 항상 최신 상태로 유지된다는 장점도 있다. </p>
<p><strong>2. 지속적인 배포/전달 (Continuous Deployment/Delivery)</strong>
<strong>- 자동화된 배포 프로세스:</strong> 코드를 수동으로 배포하는 대신, 자동화된 배포 파이프라인을 통해 새로운 기능이나 수정 사항이 신속하게 사용자에게 전달되고, 자동화된 배포 과정은 사람이 배포시 할 수 있는 오류를 줄이고 배포 속도를 높이는데 용이하다. 
<strong>- 짧은 릴리즈 주기:</strong> 작은 변경 사항을 자주 배포함으로써, 제품의 릴리즈 주기를 단축할 수 있다. 이를 통해 사용자 피드백을 빠르게 수집하고 반영할 수 있습니다.
<strong>- 안정성 및 신뢰성 향상:</strong> 자동화된 테스트와 배포를 통해, 새로운 코드가 항상 일관된 방법으로 배포되므로, 제품의 안정성과 신뢰성이 향상됩니다.
<strong>- 롤백 및 복구 용이성:</strong> 문제가 발생할 경우, 사람이 직접 롤백하면 미리 백업을 하고 백업한 버전을 적용하는데 시간이 걸리는데, 자동화된 배포 시스템은 이전 안정 버전으로 빠르게 롤백할 수 있어, 서비스 중단 시간을 최소화할 수 있다. </p>
<blockquote>
<p>이 두가지의 장점을 찾아 봤을때 나에게 가장 와닿았던거는 안정적으로 배포가 가능하다는 점이였다, 현재 일하는 회사에서는 수동 배포를 해서 변경 파일을 말아서 ftp로 전송하는 방법으로 배포를 하는데 실수할까봐 긴장되기도 하고... <del>실제로도 초반에는 몇번... 필요한 파일을 빼고 올리는 사고를 친 경험도 있다.</del></p>
</blockquote>
<h3 id="사전-준비">사전 준비</h3>
<p>필요한 도구 및 계정으로는 <strong>AWS 계정</strong> , <strong>gitHub 계정</strong>,  <strong>EC2 인스턴스 (Amazon Linux 2 또는 Ubuntu)</strong>, <strong>SSH 키 생성 및 다운로드</strong> 등이 있는데 나는 SSH키를 <strong>EC2 인스턴스 생성시 키페어 방식으로 만들고 .pem 파일을 다운</strong> 받았다. 그리고 당연하지만 CI/CD를 할 프로젝트가 필요하다. 나는 스프링부트 프로젝트를 gradle 방식으로 간단하게 설정해서 생성했다. </p>
<blockquote>
<p>여기서 중요한건 빌드가 잘 되는지 로컬에서 꼭 확인해봐야 한다. 특히 데이터베이스 연결을 잘 확인하자. 우리는 바보처럼 데이터베이스를 만들지 않고 연결을 해서 계속 오류가 났었다...🥺</p>
</blockquote>
<h3 id="aws-ec2-설정">AWS EC2 설정</h3>
<p>인스턴스를 만들고 확인해야하는 부분이 있다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/c19b919e-cc46-4cca-8415-dba6ee166d0c/image.png" alt="">
우선 이렇게 인바운드 규칙에 들어가서 보안그룹 규칙이 포트 22, 80, 443에 대해 열려있는지 확인해야한다. 접근을 허용할 부분에 대해서 잘 입력해주자.</p>
<h3 id="github-actions을-위한-설정">gitHub Actions을 위한 설정</h3>
<ol>
<li>깃허브 액션에서 워크플로우 파일을 생성한다. 예를들어 ci/cd for Spring boot 라는 이름의 yml 파일을 생성한다. 
나는 처음에 구글링을 통해<pre><code class="language-yaml">name: CI/CD for Spring Boot
</code></pre>
</li>
</ol>
<p>on:
  push:
    branches: [ &quot;main&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]</p>
<p>jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read</p>
<pre><code>steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
  uses: actions/setup-java@v4
  with:
    java-version: &#39;17&#39;
    distribution: &#39;temurin&#39;

- name: Setup Gradle
  uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0

- name: Grant execute permission for gradlew
  run: chmod +x gradlew

- name: Build with Gradle
  run: ./gradlew build

- name: Run tests
  run: ./gradlew test</code></pre><p>  deploy:
    needs: build
    runs-on: ubuntu-latest</p>
<pre><code>steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
  uses: actions/setup-java@v4
  with:
    java-version: &#39;17&#39;
    distribution: &#39;temurin&#39;

- name: Grant execute permission for gradlew
  run: chmod +x gradlew

- name: Build with Gradle
  run: ./gradlew build

- name: Copy files via SCP
  env:
    AWS_EC2_USER: ${{ secrets.AWS_EC2_USER }}
    AWS_EC2_HOST: ${{ secrets.AWS_EC2_HOST }}
    AWS_EC2_KEY: ${{ secrets.AWS_EC2_KEY }}
  run: |
    echo &quot;${{ secrets.AWS_EC2_KEY }}&quot; &gt; ec2_key.pem
    chmod 600 ec2_key.pem
    scp -i ec2_key.pem -o StrictHostKeyChecking=no build/libs/*.jar $AWS_EC2_USER@$AWS_EC2_HOST:~/app/

- name: SSH and Deploy
  env:
    AWS_EC2_USER: ${{ secrets.AWS_EC2_USER }}
    AWS_EC2_HOST: ${{ secrets.AWS_EC2_HOST }}
    AWS_EC2_KEY: ${{ secrets.AWS_EC2_KEY }}
  run: |
    ssh -i ec2_key.pem -o StrictHostKeyChecking=no $AWS_EC2_USER@$AWS_EC2_HOST &lt;&lt; &#39;EOF&#39;
      pgrep java | xargs kill -9 || true
      nohup java -jar ~/app/*.jar &gt; log.txt 2&gt;&amp;1 &amp;
    EOF</code></pre><pre><code>이런 파일을 그냥 냅다 넣었다~~...여기서 부터였을까요...삽질의 시작이🫠~~

2. 시크릿 설정
위까지 했으면 다음으로 배포를 위해 필요한 시크릿 값을 설정해야한다. 깃허브 리포지토리의 Settings -&gt; Secrets and variables -&gt; Actions 에서 New repository secret을 클릭하면 필요한 시크릿을 추가 할 수 있다. 
- AWS_EC2_USE
- AWS_EC2_HOST
- AWS_EC2_KEY 
이부분이고 AWS_EC2_USE는 보통 ec2-user 또는 ubuntu 라는데 ubuntu를 썼고, AWS_EC2_HOST는 AWS EC2에 들어가서 탄력적 IP 주소를 넣어줬다. 
&gt; 🧐왜 탄력적IP사용이 필요할까?
EC2의 인스턴스의 퍼블릭IP는 인스턴스 시작/중지시 변경될 수 있다. 탄력적 IP는 고정된 퍼블릭 IP를 제공하기 때문에 인스턴스를 재시작 하더라도 IP 주소가 변경 되지 않는다. 따라서 고정된 IP 주소를 사용하면 gitHub Actions 과 같은 외부 서비스에서 항상 동일한 IP 주소를 사용해 EC2 인스턴스에 안정적이고 일관되게 접근이 가능하다. 

그리고 마지막 AWS_EC2_KEY 이 부분이 엄청난 문제였는데, 나는 
1. 이걸 인코딩 없이 그냥 넣었고
2. 인코딩 한 후에는 권한이 너무 개방적이라서 문제가 되었다.
&gt; 따라서 꼭 권한을 주고, 그걸 인코딩해서 넣어줘야 한다!

보통 EC2 인스턴스에 연결하기 위한 키페어 파일을 인스턴스 설정시 받아서 pc에 보관중일텐데, 이 파일의 내용을 GitHub Secrets에 추가해야 한다. 

1. 권한을 변경해준다.(나만 읽고 쓰고 할 수 있다)
```bash
chmod 600 ~/Downloads/my-key-pair.pem</code></pre><ol start="2">
<li>Base64 인코딩을 한다. </li>
</ol>
<pre><code class="language-bash">base64 ~/Downloads/my-key-pair.pem</code></pre>
<p>보통 이렇게들 많이 하던데 나는 이렇게 하니까 오류가 났고 그래서 그냥 cat을 사용해 복사한 내용을 넣어서 문제가 되었다.</p>
<pre><code class="language-bash">base64 -i ~/Downloads/my-key-pair.pem -o ~/Downloads/my-key-pair.pem.base64
// SSH 키 파일을 Base64로 인코딩


cat my-key-pair.pem.base64 // 인코딩된 파일 내용 복사
</code></pre>
<p>인코딩시 반드시 이렇게 사용해주자...!</p>
<ol start="3">
<li>출력된 문자열을 복사하고 해당 문자열을 붙여준다. </li>
</ol>
<h4 id="최종으로-생성된-gradleyml">최종으로 생성된 gradle.yml</h4>
<p>그래서 여러번의 시도와 실패를 거쳐 완성된 .yml은 이렇다. </p>
<pre><code class="language-yaml">name: CI/CD for Spring Boot

on:
  push:
    branches: [ &quot;main&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;

      - name: Cache Gradle packages
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: gradle-home-v1-${{ runner.os }}-build-${{ hashFiles(&#39;**/*.gradle*&#39;, &#39;**/gradle-wrapper.properties&#39;) }}
          restore-keys: |
            gradle-home-v1-${{ runner.os }}-build-

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build --no-daemon --warning-mode all -x test  # 테스트를 생략하는 옵션 추가

  deploy:
    needs: build
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: &#39;17&#39;
          distribution: &#39;temurin&#39;

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build --no-daemon --warning-mode all -x test  # 테스트를 생략하는 옵션 추가

      - name: Decode and save SSH key
        run: echo &quot;${{ secrets.CICD_SECRET_KEY }}&quot; | base64 --decode &gt; ec2_key.pem
        shell: bash

      - name: Set permission for SSH key
        run: chmod 600 ec2_key.pem

      - name: Create target directory on EC2
        env:
          AWS_EC2_USER: ${{ secrets.CICD_ACCESS_USER }}
          AWS_EC2_HOST: ${{ secrets.CICD_ACCESS_HOST }}
        run: |
          ssh -i ec2_key.pem -o StrictHostKeyChecking=no $AWS_EC2_USER@$AWS_EC2_HOST &#39;mkdir -p ~/app/&#39;

      - name: Copy files via SCP
        env:
          AWS_EC2_USER: ${{ secrets.CICD_ACCESS_USER }}
          AWS_EC2_HOST: ${{ secrets.CICD_ACCESS_HOST }}
        run: |
          scp -i ec2_key.pem -o StrictHostKeyChecking=no build/libs/*.jar $AWS_EC2_USER@$AWS_EC2_HOST:~/app/

      - name: SSH and Deploy
        env:
          AWS_EC2_USER: ${{ secrets.CICD_ACCESS_USER }}
          AWS_EC2_HOST: ${{ secrets.CICD_ACCESS_HOST }}
        run: |
          ssh -i ec2_key.pem -o StrictHostKeyChecking=no $AWS_EC2_USER@$AWS_EC2_HOST &lt;&lt; &#39;EOF&#39;
            pgrep java | xargs kill -9 || true
            nohup java -jar ~/app/*.jar &gt; log.txt 2&gt;&amp;1 &amp;
          EOF</code></pre>
<p>이것은 초기에 아무런 생각없이 작성된 .yml 과 다음과 같은 차이가 있다. </p>
<ol>
<li>Gradle 패키지 캐시</li>
</ol>
<ul>
<li>초기 : 캐시 단계 없음</li>
<li>최종 :
``` yaml</li>
<li>name: Cache Gradle packages
uses: actions/cache@v3
with:
  path: |<pre><code>~/.gradle/caches
~/.gradle/wrapper</code></pre>  key: gradle-home-v1-${{ runner.os }}-build-${{ hashFiles(&#39;<strong>/<em>.gradle</em>&#39;, &#39;</strong>/gradle-wrapper.properties&#39;) }}
  restore-keys: |<pre><code>gradle-home-v1-${{ runner.os }}-build-</code></pre><pre><code>&gt; Gradle 패키지를 캐시하여 빌드 프로세스를 가속화하고 변경되지 않은 종속성을 재사용하도록 변경하였다. </code></pre></li>
</ul>
<ol start="2">
<li>빌드 시 테스트 생략</li>
</ol>
<ul>
<li>초기 : 빌드 중 테스트를 실행하는 코드가 있었다. 
```yaml</li>
<li>name: Build with Gradle
run: ./gradlew build
```</li>
<li>최종 :  빌드 중 테스트 생략.
```yaml</li>
<li>name: Build with Gradle
run: ./gradlew build --no-daemon --warning-mode all -x test<pre><code>&gt; CI/CD 파이프라인에서 배포에 중점을 두고 테스트 실패로 인해 생기는 문제를 피하기 위해 테스트를 생략했는데, 이부분은 추후 추가할 예정이다. </code></pre></li>
</ul>
<ol start="3">
<li>SSH 키 디코딩 및 사용</li>
</ol>
<ul>
<li>초기 : SSH 키를 직접 시크릿에서 echo로 출력함
``` yaml</li>
<li>name: Copy files via SCP
env:
  AWS_EC2_USER: ${{ secrets.AWS_EC2_USER }}
  AWS_EC2_HOST: ${{ secrets.AWS_EC2_HOST }}
  AWS_EC2_KEY: ${{ secrets.AWS_EC2_KEY }}
run: |
  echo &quot;${{ secrets.AWS_EC2_KEY }}&quot; &gt; ec2_key.pem
  chmod 600 ec2_key.pem</li>
</ul>
<pre><code>- 최종 : SSH 키를 base64 디코딩하여 사용함
```yaml
- name: Decode and save SSH key
  run: echo &quot;${{ secrets.CICD_SECRET_KEY }}&quot; | base64 --decode &gt; ec2_key.pem
  shell: bash

- name: Set permission for SSH key
  run: chmod 600 ec2_key.pem</code></pre><blockquote>
<p>Base64 인코딩/디코딩을 통해 시크릿 관리 시스템 내에서 SSH 키를 안전하고 올바르게 처리한다. </p>
</blockquote>
<ol start="4">
<li>EC2에 디렉토리 생성</li>
</ol>
<ul>
<li>초기 : 디렉토리 생성을 명시하지 않은 문제 있었음</li>
<li>최종 : EC2에 타겟 디렉토리를 생성하는 단계 추가
```yaml</li>
<li>name: Create directory on EC2
env:
  AWS_EC2_USER: ${{ secrets.CICD_ACCESS_USER }}
  AWS_EC2_HOST: ${{ secrets.CICD_ACCESS_HOST }}
run: |
  ssh -i ec2_key.pem -o StrictHostKeyChecking=no $AWS_EC2_USER@$AWS_EC2_HOST &quot;mkdir -p ~/app/&quot;<pre><code>&gt; 배포 전 필요한 디렉토리를 생성하여 파일 복사 중 오류를 방지함.


</code></pre></li>
</ul>
<h3 id="결론">결론</h3>
<p>남들은 gitHub Actions 을 사용해서 쉽게 CI/CD를 할 수 있다는데, 나는 생각보다 오래 걸린거 같다. 처음 해봐서 그런것도 있고... 너무 일단 해보고 안되면 돌아가는 과정이 많았던거 같다. 그래도 한번 해봤고 글로 정리도 했으니까 같은 실수를 반복하는 일은 없지 않을까? 
혹시 나와 비슷한 경우가 있어서 오류가 있던 사람들도 이 글이 도움이 되면 좋겠다😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 프로젝트 실행 오류]]></title>
            <link>https://velog.io/@2-seulgi/Spring-Boot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%A4%ED%96%89-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@2-seulgi/Spring-Boot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%A4%ED%96%89-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Thu, 28 Dec 2023 13:37:41 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>스프링 부트를 공부한다고 회사에서 시간 날때 조금씩 연습한 코드가 있는데, 이걸 git clone으로 받아서 집에 있는 맥북으로 실행했을때 엄청난 길이의 에러 메세지가 발생하며 실행이 되지 않았다. </p>
<blockquote>
</blockquote>
<p>A problem occurred configuring root project &#39;servlet&#39;.</p>
<blockquote>
<p>Could not resolve all files for configuration &#39;:classpath&#39;.
Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.2.0.
     Required by:
         project : &gt; org.springframework.boot:org.springframework.boot.gradle.plugin:3.2.0
No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.2.0 was found. The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute &#39;org.gradle.plugin.api-version&#39; with value &#39;8.5&#39; but:
          - Variant &#39;apiElements&#39; capability org.springframework.boot:spring-boot-gradle-plugin:3.2.0 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn&#39;t say anything about org.gradle.plugin.api-version (required &#39;8.5&#39;)
          - Variant &#39;javadocElements&#39; capability org.springframework.boot:spring-boot-gradle-plugin:3.2.0 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn&#39;t say anything about its target Java version (required compatibility with Java 11)
                  - Doesn&#39;t say anything about its elements (required them packaged as a jar)
                  - Doesn&#39;t say anything about org.gradle.plugin.api-version (required &#39;8.5&#39;)
          - Variant &#39;mavenOptionalApiElements&#39; capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.2.0 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn&#39;t say anything about org.gradle.plugin.api-version (required &#39;8.5&#39;)
          - Variant &#39;mavenOptionalRuntimeElements&#39; capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.2.0 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn&#39;t say anything about org.gradle.plugin.api-version (required &#39;8.5&#39;)
          - Variant &#39;runtimeElements&#39; capability org.springframework.boot:spring-boot-gradle-plugin:3.2.0 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn&#39;t say anything about org.gradle.plugin.api-version (required &#39;8.5&#39;)
          - Variant &#39;sourcesElements&#39; capability org.springframework.boot:spring-boot-gradle-plugin:3.2.0 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn&#39;t say anything about its target Java version (required compatibility with Java 11)
                  - Doesn&#39;t say anything about its elements (required them packaged as a jar)
                  - Doesn&#39;t say anything about org.gradle.plugin.api-version (required &#39;8.5&#39;)</p>
</blockquote>
<p>진짜 엄청길어서 눈에 잘 들어오지도 않았다. </p>
<p>내가 이번에 깃으로 클론 받은 프로젝트는 스프링부트 3.2.0를 사용하고 있었고, 스프링 부트 3.0 부터는 Java17부터 지원한다. 내 프로젝트가 구동이 안되는 이유는 바로 여기에 있었다. </p>
<h2 id="해결">해결</h2>
<h4 id="1-java--version">1. Java -version</h4>
<p>해당 명령어로 내 자바 버전을 확인 했다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/9f1a6a90-7053-4b18-b302-9a5911b23ada/image.png" alt="">
JDK    버전이 11이다 🫠. 생각해보니 내 맥북으로는 테무린 11 버전으로 간단한 공부를 하고 있어서 이렇게 설정되어 있었다.  </p>
<h4 id="2-jdk-버전-수동으로-변경">2. JDK 버전 수동으로 변경</h4>
<p>나처럼 개발을 하다보면 JDK버전을 중간중간 변경해야할 때가 있을거다. (참고로 나는 macOS의 zsh쉘 환경에서 환경변수를 설정했다)
<code>/usr/libexec/java_home -V</code>
이 명령어를 사용해 설치된 Java Virtual Machines 목록을 확인 해본다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/5bb4716d-86c1-4213-ba7e-a3392d658a26/image.png" alt="">
...😳오지게도 많이 설치했다...</p>
<p>우선, 
<code>echo &#39;export JAVA_HOME=/usr/local/opt/openjdk@21&#39; &gt;&gt; ~/.zshrc</code>
해당 명령어를 실행한다. </p>
<p><code>export JAVA_HOME=/usr/local/opt/openjdk@21&#39; :</code> JAVA_HOME 환경 변수를 설정하는 명령다. 이 변수는 자바 설치 경로 즉 openjdk21이 설치된 위치를 나타낸다. 
<code>&gt;&gt; ~/.zshrc :</code> 이 부분은 JAVA_HOME=/usr/local/opt/openjdk@21라는 문자열을 사용자의 홈 디렉토리에 있는 .zshrc 파일 끝에 추가하는 명령어이다. .zshrc는 zsh쉘의 환경 설정 파일로, 쉘을 시작할 때마다 실행된다. </p>
<p>다음으로는,
<code>echo &#39;export PATH=$JAVA_HOME/bin:$PATH&#39; &gt;&gt; ~/.zshrc</code> 명령어를 실행한다. 
<code>export PATH=$JAVA_HOME/bin:$PATH :</code> 이 명령어는 시스템의 실행 파일 경로에 Java의 bin 디렉토리를 추가해서 자바 실행 파일들이 시스템의 어디서나 실행될 수 있게 해준다. </p>
<p>이후,
<code>source ~/.zshrc</code>를 실행해 변경 사항을 저장해주면 된다. </p>
<p>위와 같이 쉘을 시작할 때마다 자동으로 자바 환경 변수를 설정하도록 해주는 명령어를 사용해 수동으로 JDK 버전을 변경 할 수 있다. </p>
<p>이렇게 까지 했는데도 빌드가 안되고 오류가 발생한다면 다른 설정을 확인해 봐야한다. </p>
<h4 id="3-gradle-설정-확인">3. Gradle 설정 확인</h4>
<p>build.gradle 파일을 확인하여 필요한 의존성이 올바르게 선언되었는지 확인한다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/53c3ac65-75df-440f-ba51-47028cb773dd/image.png" alt="">
이 부분을 확인해준다. </p>
<p>나는 여기까지 확인했는데도 오류가 있었다. 그럴 수 밖에 없지 인텔리제이도 기존에 설정된 자바 11로 프로젝트를 실행할테니까 ~ 아주 흥이다 😤 </p>
<h4 id="4-intellij-설정-확인">4. IntelliJ 설정 확인</h4>
<p>IntelliJ의 Preferences/Settings -&gt; Build, Execution, Deployment -&gt; Build Tools -&gt; Gradle 로 이동한다. </p>
<p>아래와 같은 설정중 Gradle의 JVM이 Java 17이상인지 확인한다.
<img src="https://velog.velcdn.com/images/2-seulgi/post/47b7d892-7875-4d60-ba6d-55193a7a928a/image.png" alt=""></p>
<p>그리고 다음으로 프로젝트 SDK의 버전을 확인하면 된다.
이번에는 file-&gt;Project Structure.. 로 이동해서 여기도 java 17 이상으로 설정해준다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/b090b9ad-83de-454c-ace2-fe6ad681537d/image.png" alt="">
이렇게 적용을 끝내준다. </p>
<p>이후 Gradle Refresh를 하면 정상적으로 빌드가 되는 것을 확인 할 수 있다😌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] ! [rejected] 오류 해결]]></title>
            <link>https://velog.io/@2-seulgi/Git-rejected-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@2-seulgi/Git-rejected-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Tue, 19 Dec 2023 06:02:30 GMT</pubDate>
            <description><![CDATA[<h2 id="1-rejected-main--main-fetch-first">1. ![rejected] main-&gt; main (fetch first)</h2>
<p><img src="https://velog.velcdn.com/images/2-seulgi/post/040a247d-a1b2-48b1-9aff-7aa9b22bd01c/image.png" alt="">
로컬 저장소에 있는 프로젝트를 GitHub에 있는 원격 저장소에 처음으로 push 하려고 할 때 (git push -u origin main) 위 사진과 같은 오류가 발생했다. 
**[rejected] main-&gt; main (fetch first) **
처음에는 예상치 못한 오류에 당황 했지만 로그가 제법 친절해서 로그를 보니 원격에 있는 변화를 통합하기 위해 git pull 을 하면 된다고 해서 pull 을 하고 다시 git push -u origin main를 했는데...</p>
<h2 id="2-rejected-main--main-non-fast-forward">2. ![rejected] main-&gt; main (non-fast-forward)</h2>
<p><img src="https://velog.velcdn.com/images/2-seulgi/post/f4fe3519-00fa-4b98-9fb8-bd362edd8a54/image.png" alt=""></p>
<p>아니 선생님 ... 
살펴보니 이건 내 로컬 git 브랜치가 원격 브랜치보다 뒤라서 push가 안된다는거 같다. pull을 했는데 왜?</p>
<p>생각해보니 위에서 git pull을 했을때 잘 된게 아니라 
<img src="https://velog.velcdn.com/images/2-seulgi/post/fa86f393-c958-41a5-a529-fed1732bfa63/image.png" alt="">
이런 문제가 있었다... 이걸 무시하고 다시 git push -u origin main를 하니까 문제가 또 생기지🙄... </p>
<p>그래서 이번에는 
<code>$ git pull origin main --allow-unrelated-histories</code>
을 사용해서 관련 없는 두 저장소를 병합하도록 허용해주었다. </p>
<pre><code class="language-bash"> * branch            main       -&gt; FETCH_HEAD
Merge made by the &#39;ort&#39; strategy.
 README.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 README.md</code></pre>
<p>이번에는 잘 된거 같다 main 브랜치를 원격 저장소에서 로컬 저장소로 가져왔고, ort 전략으로 병합 작업을 수행했으며<del>(...ort가 뭔지는 추후 공부가 필요할거 같다)</del>, 따라서 README.md 파일이 잘 생성된거 같다.</p>
<p><code>$ git push -u origin main</code> 이후 다시 해당 명령어를 수행하였고, 이번에는 push가 잘 이루어 졌다. </p>
<p>아마도 GitHub에 레파지토리를 만들면서 README.md파일을 추가했는데 이에 대한 제대로 된 과정이 없이 push 작업을 시도한게 주 원인 같다. </p>
<p>현업에서 일을 하고 있지만 svn을 사용해서 그런가 git과 GitHub에 대해서는 쉽게 익숙해지지 않는다. 아무래도 자주 git 명령어를 사용해보고 로컬저장소와 원격 저장소에 pull, push를 해보고 다양한 오류를 해결해보면서 익숙해져야 할거 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[옵셔널 체이닝 '?.']]></title>
            <link>https://velog.io/@2-seulgi/%EC%98%B5%EC%85%94%EB%84%90-%EC%B2%B4%EC%9D%B4%EB%8B%9D-</link>
            <guid>https://velog.io/@2-seulgi/%EC%98%B5%EC%85%94%EB%84%90-%EC%B2%B4%EC%9D%B4%EB%8B%9D-</guid>
            <pubDate>Sat, 30 Apr 2022 06:08:08 GMT</pubDate>
            <description><![CDATA[<h2 id="옵셔널-체이닝이란">옵셔널 체이닝이란</h2>
<blockquote>
<p>옵셔널 체이닝(optional chaining) 은 새로운 연산자이다. &#39;?.&#39;의 형태로 사용하는 이 연산자는 체인으로 이루어진 각 참조가 유효한지 명시적으로 검증하지 않고 연결된 객체 체인 내에 깊숙히 위치한 속성 값을 읽을 수 있다. </p>
</blockquote>
<h2 id="옵셔널-체이닝을-사용하는-이유">옵셔널 체이닝을 사용하는 이유</h2>
<p>옵셔널 체이닝(optional chaining) <code>?.</code>을 사용하면 프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있습니다.
예를 들어 중첩된 구조를 가진 객체에서 옵셔널 체이닝 없이 중첩된 하위 속성을 찾기 위해서는 다음과 같이 <code>&amp;&amp;</code> 연산자를 사용했다.</p>
<pre><code class="language-js">let nestedProp = obj.first &amp;&amp; obj.first.second;</code></pre>
<p>그러나 옵셔널 체인징 연산자를 사용하면 </p>
<pre><code class="language-js">let nestedProp = obj.first?.second;</code></pre>
<p>이런식으로 obj.first.second 에 접근하기 전에 obj.first를 테스트하지 않아도 된다. <code>.</code>대신 <code>?.</code>를 사용하면서 자바스크립트는 obj.first가 null 또는 undefined가 아니라는 것을 확인한다. 만약 obj.first가 null 또는 undefined 라면 평가를 멈추고 undefined를 반환한다.</p>
<h2 id="옵셔널-체인징의-사용">옵셔널 체인징의 사용</h2>
<p>옵셔널 체이닝 문법은 직관적이고 사용하기도 편합니다. <code>?.</code> 연산자 왼쪽의 평가 대상이 null이나 undefined인지 확인하고 아니라면 평가를 계속 진행한다. <code>?.</code>연산자를 계속 연결해서 체인을 만들면 중첩 프로퍼티에 안전하게 접근 가능하지만 <code>?.</code> 왼쪽 평가대상이 없어도 괜찮은 경우에만 선택적으로 사용해야 합니다. </p>
<p>출처 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining">MDN</a> , <a href="https://ko.javascript.info/optional-chaining">javascript.info</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이널프로젝트 오류 기록]]></title>
            <link>https://velog.io/@2-seulgi/%ED%8C%8C%EC%9D%B4%EB%84%90%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%98%A4%EB%A5%98-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@2-seulgi/%ED%8C%8C%EC%9D%B4%EB%84%90%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%98%A4%EB%A5%98-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Fri, 22 Apr 2022 17:26:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/2-seulgi/post/f2481eb8-1801-4e6a-b28a-3e414e08821e/image.png" alt="">내 코드는 너무너무 오류가 많아🙃</p>
<h2 id="매일매일-하는-삽질"><del>매일매일 하는</del> 삽질</h2>
<p>이제 찐으로 얼마 안남은 파이널프로젝트를 진행하던 중 사실 그동안은 촉박하고 너무 얼레벌레 진행해서 기록할 생각을 못했는데...이번엔 주말이기도 하고 조금 시간이 있어서 오류 기록을 해봤다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/709c12c5-c6a6-4877-95c6-b887dd768c1f/image.png" alt="">
내가 구현하고 싶은것. 서버에서 유저의 구독 정보를 가져와서 화면에 구독중이면 밝게, 아니면 어둡게 처리하고 싶었다. </p>
<p>진짜 초면인거 같은 리액트로 <del>거의 핥듯이 배워서 파이널 하면서 다시 공부중</del> 콘솔에 구독 정보 찍는거 까진 성공을 했는데 
이걸 어떻게 화면에 표현하냐...를 고민하다가 className을 active와 disabled를 사용해서 true일땐 active, false면 disabled가 나오도록 삼항연산자를 사용해 구현하면 될거 같아서 그렇게 해보기로 했다. </p>
<pre><code class="language-javascript">const [isOtt, setIsOtt] = useState();
  const userCode = 1; //임시
  useEffect(() =&gt; {
    (async () =&gt; {
      try {
        const res = await axios.get(`${BASE_URL}/user/${userCode}/ott`);
        // res.data? setIsOtt(true) : setIsOtt(false);
        console.log(res.data);
        setIsOtt(res.data);
      } catch (e) {
        console.error(e);
      }
    })();
  }, []);
  return (
      &lt;Button id=&quot;ott_logo&quot; className={isOtt ? &#39;active&#39; : &#39;disabled&#39;}&gt;&lt;img src = &quot;&quot;&gt;&lt;/Button&gt;
  )</code></pre>
<p>대충 생략할건 하고... 이런 코드를 짰다. </p>
<h3 id="여기서-첫번째-멍청한-짓-발생">여기서 첫번째 멍청한 짓. 발생!</h3>
<p>모든 버튼이 active로 나와서 다 밝게 보이는 문제가 생겼다. 진짜 한참을 혼자 삽질하다가 결국 개발자 오픈카톡방 가서 질문해서 해결함😅
className에서 isOtt가 있냐 확인하는데 isOtt 값은 {netflix: true, wavve:false, tving: true}이런 식인데 내가 진짜 너무 멍청하게도 그냥 isOtt ? 이러고 있으니 해당 ott가 true인지 false인지 확인을 못하는게 당연했던것.</p>
<p>지금 글 쓰면서 당연하다 생각하는데 하는데 그 당시에는 어떻게 하는건데 하면서 계속 삽질하고 있었다🥲 도대체...</p>
<pre><code class="language-js">return (
      &lt;Button id=&quot;ott_logo&quot; className={isOtt.netflix ? &#39;active&#39; : &#39;disabled&#39;}&gt;&lt;img src = &quot;&quot;&gt;&lt;/Button&gt;
  )</code></pre>
<p>아무튼 이런식으로 코드를 변경했다. 그러니까 안됨. </p>
<h3 id="이번엔-또-뭔데아무튼-두번째-오류-발생">이번엔 또 뭔데...아무튼, 두번째 오류. 발생!</h3>
<p>이번엔 화면에 아무것도 안보이더라... 단톡방에서 알려주신 분이 예상하신대로 콘솔창을 가보니 거기엔 또 오류가 터지고 있었다🤯<img src="https://velog.velcdn.com/images/2-seulgi/post/bfc66c16-c7ce-4b2d-9b0a-a0e818c50f85/image.png" alt="">보기만 해도 괴롭다 진짜.. 암튼 undefined 오류가 발생했다. 이럴때는 optional chainning 연산자 ?. 을 사용하라고 해서 </p>
<pre><code class="language-js">return (
      &lt;Button id=&quot;ott_logo&quot; className={isOtt?.netflix ? &#39;active&#39; : &#39;disabled&#39;}&gt;&lt;img src = &quot;&quot;&gt;&lt;/Button&gt;
  )</code></pre>
<p>했더니, <img src="https://velog.velcdn.com/images/2-seulgi/post/ac5abaec-0381-45b9-9116-6b8edc720993/image.png" alt="">
짜잔 원하던 대로 구현이 되었다. 데이터를 바꿔주면 
<img src="https://velog.velcdn.com/images/2-seulgi/post/d22be66f-2e06-40b7-b0e2-99742407a176/image.png" alt=""> 이렇게 잘 바뀌어서 나오는것을 확인 할 수 있다. </p>
<p>옵셔널 체인징에 대해서는 별도로 글을 남기고 링크 추가할 예정!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트에서 Ajax사용해보기]]></title>
            <link>https://velog.io/@2-seulgi/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-Ajax%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@2-seulgi/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-Ajax%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 16 Apr 2022 17:33:01 GMT</pubDate>
            <description><![CDATA[<h1 id="ajax란-무엇인가">Ajax란 무엇인가?</h1>
<blockquote>
<p>서버에 새로고침없이 요청을 가능하게 하는 일종의 자바스크립트 코드.</p>
</blockquote>
<h4 id="간단하게-서버와-요청에-대해서도-알아보자🧐">간단하게 서버와 요청에 대해서도 알아보자🧐</h4>
<ul>
<li><p>서버
요청을 페이지를 요청하면 그 페이지 데이터를 가져다주는것. 예를들어 내가 주소창에 netflex.쳐서 접속하면(요청) 넷플릭스 메인페이지가 나오는것(결과)을 말한다. </p>
</li>
<li><p>요청</p>
</li>
</ul>
<ol>
<li>get방식
url을 쳐서 요청한다. <strong>자료를 읽을때</strong> 사용한다. </li>
<li>post방식
url에 치는게 아니라 블로그에 댓글을 남기거나 사이트 로그인 할때 아이디랑 비밀번호를 치고 특정한 버튼을 눌러서 요청하는 방식을 말한다. <strong>서버로 정보를 전달</strong> 할때 사용한다.  </li>
</ol>
<p>보통 서버로 get방식/post방식을 사용해 요청할때 새로고침이 일어나는 것을 흔하게 볼 수 있다. 그러나 Ajax를 사용하면 서버에 새로고침 없이 요청을 할 수 있게 도와준다. </p>
<h1 id="ajax-사용">Ajax 사용</h1>
<ol>
<li>jQuery Ajax - 가장 흔하게 사용하는 방법</li>
<li>axios 라는 라이브러리 설치 후 사용 - vue, 리액트에서 많이 사용</li>
<li>자바스크립트 문법인 fetch() 사용</li>
</ol>
<p>보통 react나 vue는 2,3 많이 사용한다. 나는 2를 사용하는 방법으로 공부해보기로 함.
터미널에 둘 중 하나 입력해서 axios를 설치한다. 
<code>yarn add axios</code>
<code>npm install axios</code>
설치가 완료 되면 import 해서 사용하면 된다. </p>
<p>*<em>1. 서버의 데이터를 가져오는 get방식의 기본적인 형식은 대충 이런 느낌이다. *</em></p>
<pre><code class="language-jsx">대충 여러 import 있음
import axios from &#39;axios&#39;;

function App(){
return(
 &lt;button onClick={()=&gt;{
     axios.get(&#39;get요청url&#39;);
 }}&gt;클릭&lt;/button&gt;
)</code></pre>
<p>저 url을 만들어주는 부분은 보통 서버 개발자의 영역이다. </p>
<p>*<em>2. 서버에 데이터를 보내는 post방식의 기본적인 형식
post 방식은 데이터를 전송할 url과 전송할 데이터 이 두가지 항목을 입력해야한다. *</em></p>
<pre><code class="language-jsx">대충 여러 import 있음
import axios from &#39;axios&#39;;

function App(){
return(
 &lt;button onClick={()=&gt;{
     axios.post(&#39;url&#39;,{title:&#39;안녕하세요&#39;, content:&#39;잘부탁드립니다.&#39;});
 }}&gt;클릭&lt;/button&gt;
)</code></pre>
<p>로그인 할때 아이디와 패스워드를 전송하는 방법은 이런 느낌이다. </p>
<p>리액트 뿐만 아니라 여러 언어를 배울때마다 느끼는 부분인데 대충 이런 느낌인지 아는건 그냥 저냥 하겠는데 활용하는게 정말 어려운 부분인거 같다. 
대충 요청한 데이터를 state에 추가해서 활용하면 될거 같긴한데 막상 코드를 짜면 또 어렵고😥 잘 안되는 느낌이라 <del>종종</del> 속상하다. 
<img src="https://velog.velcdn.com/images/2-seulgi/post/54d5604b-a9fe-462b-8141-f35b7910085f/image.png" alt="">
그치만 계속 꾸준히 하다보면 개발자스러운 사고도 노력으로 어느정도 할 수 있지 않을까. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[악으로 깡으로 파이썬 웹스크래핑한 후기]]></title>
            <link>https://velog.io/@2-seulgi/%EC%95%85%EC%9C%BC%EB%A1%9C-%EA%B9%A1%EC%9C%BC%EB%A1%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9B%B9%EC%8A%A4%ED%81%AC%EB%9E%98%ED%95%91%ED%95%9C-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@2-seulgi/%EC%95%85%EC%9C%BC%EB%A1%9C-%EA%B9%A1%EC%9C%BC%EB%A1%9C-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9B%B9%EC%8A%A4%ED%81%AC%EB%9E%98%ED%95%91%ED%95%9C-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 08 Apr 2022 07:37:56 GMT</pubDate>
            <description><![CDATA[<p>사실 벌써 좀 지난 일이지만, 파이썬 안배운 사람이 악으로 깡으로 웹스크래핑한 후기를 꼭 쓰고 싶었기에 이제라도 써본다. </p>
<h3 id="파이널-프로젝트가-불러온-파급">파이널 프로젝트가 불러온 파급</h3>
<p>교육과정에서 파이널 프로젝트를 진행하면서 우리조는 우리나라 3대장 ott 서비스의 콘텐츠를 검색 및 추천하는 서비스를 구현하고자 하였다. <del>이렇게 데이터 후작업이 힘들줄 알았으면 공공 데이터를 활용하는 쪽으로 할걸</del> 아쉽게도 준비된 데이터가 없었기 때문에 우리 앞에는 선택지가 별로 없었다. 수작업 노가다를 하던가, 크롤링 즉, 웹스크래핑을 하던가. </p>
<p>우리 수업 과정에도 파이썬이 있었기 때문에 그 시간에 웹스크래핑도 끼워서 배울 수 있지 않을까 했으나, 다른 조원들도 각자 맡은 일을 하고 있었고, 나도 나대로 마음이 급했기 때문에 빠르게 내 나름대로 웹 스크래핑을 학습해서 하기로 결정했다. 지금 생각해보면 파이썬 교육은 단 2일로 어영부영 끝나서 지금 생각하면 정말 현명한 선택이였다😅.</p>
<h3 id="일단-해보기">일단 해보기</h3>
<p>처음에는 익숙한 언어인 자바로 해보려고 했으나, 이상하게 인텔리제이가 크롬드라이버를 인식하지 못해서 포기하고 바로 내 강사님 구글과 유튜브를 통해 파이썬 웹스크래핑을 학습했다. 처음에는 vscode를 사용해서 웹스크래핑을 시도했다. 
<img src="https://velog.velcdn.com/cloudflare/2-seulgi/8487a2ed-2d44-44e1-8d80-920ad929f855/image.png" alt="">
<del>이건 내 피땀눈물나는 연습기록들</del></p>
<h3 id="문제의-발생과-해결">문제의 발생과 해결</h3>
<p>문제는 넷플릭스를 해보던 중 발생했다. html 클래스에서 내가 필요한 정보만 빼오려고 이것저것 시도를 하면서 셀레니움으로 자동 로그인을 너무 빈번하게 해서 일까? 갑자기 넷플릭스 로그인이 안되는 지경에 이르렀다. 비밀번호를 바꾸고 다시 시도가 가능했지만 이래저래 불편했다. 계속 무한 스크롤을 반복해야했고, 로그인도 해줘야 하고... 그래서 셀단위 실행이 가능한 주피터 노트북을 활용해 </p>
<pre><code class="language-python">html = browser.page_source
soup = BeautifulSoup(html,&#39;html.parser&#39;)</code></pre>
<p>soup에 모든 html을 넣은 후 필요한 정보를 가져오는 것을 이런 저런 방법으로 시도 했다. 셀 단위 실행이 가능해서 일까, 실패에 대한 부담도 줄었고 확실히 너무 편했다. </p>
<h3 id="파이썬으로-한-웹스크래핑">파이썬으로 한 웹스크래핑</h3>
<p>웹스크래핑 자체는 내 생각보다 어렵진 않았다. find나 select를 잘 사용해서 원하는 class나 선택자를 잘 골라서 그 안에 들어간 데이터를 가져오는거라 html에 대한 이해가 있고, 섹션을 잘 나누고, 시간만 충분히 있다면 여러 시행착오를 거쳐 할 수 있을거라는 자신감이 생겼다. </p>
<pre><code class="language-python"># 섹션 지정
for section in section_list:

    # 프로그램 파트
    program_list = section.select(&#39;.ptrack-content&#39;)
    for program in program_list:
        program_title = program.select(&#39;.fallback-text&#39;)[0].text
        # program_img = program.select(&#39;img&#39;)[0][&#39;src&#39;]
        program_link = &quot;https://www.netflix.com&quot;+program.select(&#39;a&#39;)[0][&#39;href&#39;]
        browser = webdriver.Chrome(&#39;./chromedriver.exe&#39;)
        browser.get(program_link)
        time.sleep(1)
        html2 = browser.page_source
        soup2 = BeautifulSoup(html2,&#39;lxml&#39;)
        details= soup2.find(&quot;div&quot;,attrs={&quot;class&quot;:&quot;details-container&quot;})

        try:
            content = details.find(&quot;div&quot;,attrs={&quot;class&quot;:&quot;title-info-synopsis&quot;}).get_text()
        except:
            content =&#39;&#39;

        try:            
            actors = details.find(&quot;span&quot;, attrs={&quot;class&quot;:&quot;title-data-info-item-list&quot;}).get_text()
        except:
            actors =&#39;&#39;

        try:    
            program_year = details.find(&quot;span&quot;,attrs={&quot;class&quot;:&quot;title-info-metadata-item item-year&quot;}).get_text()
        except:
            program_year=&#39;&#39;

        try:    
            program_time = details.find(&quot;span&quot;,attrs={&quot;class&quot;:&quot;duration&quot;}).get_text()
        except:
            program_time=&#39;&#39;

        try:    
            program_age = details.find(&quot;span&quot;,attrs={&quot;class&quot;:&quot;maturity-number&quot;}).get_text()
        except:
            program_age=&#39;&#39;

        try:    
            program_tags = details.find(&quot;a&quot;,attrs={&quot;class&quot;:&quot;title-info-metadata-item item-genre&quot;}).get_text()
        except:
            program_tag=&#39;&#39;

        print(program_title,program_link,content,actors,program_year,program_time,program_age,program_tags,sep=&#39; / &#39;)
        data = [program_title,program_link,content,actors,program_year,program_time,program_age,program_tags]
        results.append(data)</code></pre>
<p>이건 내가 특정 ott 웹 스크래핑을 하기 위해 짠 코드 일부 이다. 보통은 select를 사용하는게 더 좋다고 한다. for문을 사용해 상세페이지에 들어가 상세 정보까지 가져오는것까지 여러 어려움이 있었지만 결과물을 놓고 보면 뿌듯하다. 
다만 ott별로 가져오는 정보가 조금씩 다르고, 우리 팀이 계획한 서비스는 우리만의 태그로 분류를 다시해야하기 때문에 아직도 데이터 후작업을 하고 있는건 정말...🤯...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[const, let, var]]></title>
            <link>https://velog.io/@2-seulgi/const-let-var</link>
            <guid>https://velog.io/@2-seulgi/const-let-var</guid>
            <pubDate>Wed, 06 Apr 2022 05:48:06 GMT</pubDate>
            <description><![CDATA[<p>변수만들때 let, const, var차이
let 재선언 금지, 재할당 가능
const 재선언 금지, 재할당 금지
var 재선언 가능, 재할당 가능</p>
<p>let a = b;
let a = c;
//재선언 금지</p>
<p>let a = b;
a = c;
//재할당은 가능</p>
<p>const a = b;
const a = c;
//재선언 금지</p>
<p>const a = b;
a = c;
//재할당 금지</p>
<p>var a = b;
var a = c;
a = d;
//재선언, 재할당 가능</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[콜백함수(Callback function) 정리 ]]></title>
            <link>https://velog.io/@2-seulgi/%EC%BD%9C%EB%B0%B1%ED%95%A8%EC%88%98Callback-function-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@2-seulgi/%EC%BD%9C%EB%B0%B1%ED%95%A8%EC%88%98Callback-function-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 18 Mar 2022 15:08:34 GMT</pubDate>
            <description><![CDATA[<h1 id="콜백callback함수란">콜백(Callback)함수란?</h1>
<blockquote>
<p>함수에 파라미터로 들어가는 함수이다. 자바스크립트에서 순차적으로 실행하고 싶을 때 사용한다. </p>
</blockquote>
<pre><code class="language-js">&lt;script&gt;           
  document.querySelector(&#39;.button&#39;).addEventListner(&#39;click&#39;,function(){
})
setTimeout(function(){
},1000)
&lt;/script&gt;</code></pre>
<p>addEventListner 함수와 setTimeout 함수에 파라미터로 들어있는 function()이 바로 콜백함수이다. </p>
<h2 id="콜백함수의-특징">콜백함수의 특징</h2>
<ul>
<li>다른데서 만든 함수도 함수명을 사용해 콜백함수로 넣을 수 있다.<pre><code class="language-js">&lt;script&gt;           
document.querySelector(&#39;.button&#39;).addEventListner(&#39;click&#39;,함수명 {
})</code></pre>
</li>
<li>콜백함수에 함수명을 작명 할 수 있다. <pre><code class="language-js">&lt;script&gt;           
setTimeout(function 함수명(){
},1000)
&lt;/script&gt;</code></pre>
</li>
<li>콜백함수가 필요한 함수들에만 콜백함수 사용가능하다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 논리 연산자(Logical operators)]]></title>
            <link>https://velog.io/@2-seulgi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%85%BC%EB%A6%AC-%EC%97%B0%EC%82%B0%EC%9E%90Logical-operators</link>
            <guid>https://velog.io/@2-seulgi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%85%BC%EB%A6%AC-%EC%97%B0%EC%82%B0%EC%9E%90Logical-operators</guid>
            <pubDate>Thu, 03 Mar 2022 11:08:39 GMT</pubDate>
            <description><![CDATA[<h1 id="논리-연산자logical-operators">논리 연산자(Logical operators)</h1>
<h2 id="1-or--">1. OR : ||</h2>
<blockquote>
<p>||(or)은 하나라도 true인 경우 true를 반환한다. 첫번째 true를 찾으면 연산을 멈춘다. </p>
</blockquote>
<pre><code class="language-js">const value1 = false;
const value2 = 10 &lt; 2;

console.log(`or:${value1 || value2 || check()}`);

function check() {
  for (let i = 0; i &lt; 10; i++) {
    console.log(&#39;hello&#39;);
  }
  return true; //의미 없는 함수 결국 true를 리턴한다.
}</code></pre>
<p>콘솔창에는 hello 10개와 or:true 가 찍힌다. </p>
<pre><code class="language-js">const value1 = true;
const value2 = 10 &lt; 2;

console.log(`or:${value1 || value2 || check()}`);

function check() {
  for (let i = 0; i &lt; 10; i++) {
    console.log(&#39;hello&#39;);
  }
  return true; //의미 없는 함수 결국 true를 리턴한다.
}</code></pre>
<p>콘솔창에는 or:true만 찍힌다. value1이 true기 때문에 이미 true로 끝난다. </p>
<h3 id="🧐-더-좋은코드를-생각해보자">🧐 더 좋은코드를 생각해보자.</h3>
<p>1.</p>
<pre><code class="language-js">const value1 = true;
const value2 = 10 &lt; 2;

console.log(`or:${value1 || value2 || check()}`);

function check() {
  for (let i = 0; i &lt; 10; i++) {
    console.log(&#39;hello&#39;);
  }
  return true; 
}</code></pre>
<p>2.</p>
<pre><code class="language-js">const value1 = true;
const value2 = 10 &lt; 2;

console.log(`or:${check() || value1 || value2}`);

function check() {
  for (let i = 0; i &lt; 10; i++) {
    console.log(&#39;hello&#39;);
  }
  return true; 
}</code></pre>
<p>위 두개의 코드는 둘 다 콘솔에 or:true가 찍힌다. 그러나 후자보다 전자가 더 좋은 코드이다. check()같은 함수는 뒤에 배치하는것이 좋다.</p>
<h2 id="2-and-">2. AND : &amp;&amp;</h2>
<blockquote>
<p>&amp;&amp;(and) 은 하나라도 false인 경우 false를 반환한다. 첫번째 false를 찾으면 연산을 멈춘다. 모두 true인 경우에만 true를 리턴한다. </p>
</blockquote>
<pre><code class="language-js">const value1 = false;
const value2 = 10 &lt; 2;

console.log(`and:${value1 &amp;&amp; value2 &amp;&amp; check()}`);

function check() {
  for (let i = 0; i &lt; 10; i++) {
    console.log(&#39;hello&#39;);
  }
  return true;
}</code></pre>
<p>value1이 false라 콘솔에 and:false가 찍힌다.</p>
<pre><code class="language-js">const value1 = true;
const value2 = 10 &lt; 20;

console.log(`and:${value1 &amp;&amp; value2 &amp;&amp; check()}`);

function check() {
  for (let i = 0; i &lt; 10; i++) {
    console.log(&#39;hello&#39;);
  }
  return true;
}</code></pre>
<p>이렇게 모든 연산이 true인 경우에만 콘솔에 and:true가 나온다. and 연산자 역시 가장 앞에 false 이면 더 이상 연산하지 않고 false를 반환하기 때문에 복잡한 연산은 가장 뒤에 배치하자!  </p>
<h3 id="🧐-and연산자의-또-다른-쓰임">🧐 &amp;&amp;(and)연산자의 또 다른 쓰임</h3>
<p>간편한 null 체크</p>
<pre><code class="language-js">// nullableObject이게 null이면 false라 nullableObject.something 실행하지 않음
nullableObject &amp;&amp; nullableObject.something</code></pre>
<p>이 코드를 풀어서 쓰면</p>
<pre><code class="language-js">if(nullableObject != null){
  nullableObject.something;
}</code></pre>
<p>이렇게 길어진다.  </p>
<h2 id="3---not">3. ! : not</h2>
<blockquote>
<p>값을 반대로 바꿔준다. false-&gt; true, true-&gt;false</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[스크립트의 실행 시점을 조절하는 법 (async vs defer)]]></title>
            <link>https://velog.io/@2-seulgi/%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%8B%A4%ED%96%89-%EC%8B%9C%EC%A0%90%EC%9D%84-%EC%A1%B0%EC%A0%88%ED%95%98%EB%8A%94-%EB%B2%95-async-vs-defer</link>
            <guid>https://velog.io/@2-seulgi/%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EC%8B%A4%ED%96%89-%EC%8B%9C%EC%A0%90%EC%9D%84-%EC%A1%B0%EC%A0%88%ED%95%98%EB%8A%94-%EB%B2%95-async-vs-defer</guid>
            <pubDate>Mon, 28 Feb 2022 16:29:52 GMT</pubDate>
            <description><![CDATA[<h1 id="async-vs-defer">async vs defer</h1>
<h2 id="❗script를-head-안에-작성">❗script를 head 안에 작성</h2>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Document&lt;/title&gt;
    &lt;script src =&quot;main.js&quot;&gt;&lt;/script&gt; 
&lt;/head&gt;
&lt;body&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>위 코드처럼 head 안에 script를 포함하게 될 경우, 브라우저가 한줄씩 html을 parsing한다.  그러다가 _script src = &quot;main.js&quot;를 만나게 되면 <strong>잠시 parsing을 멈추고</strong> 필요한 자바스크립트 파일을 서버에서 <strong>다운받아 실행후 다시 html parsing을 시작</strong>_한다. </p>
<blockquote>
<p>🤨 만약 인터넷이 엄청나게 느리거나, JavaScript 코드가 길어서 다운이 오래걸릴 경우 _사용자가 웹사이트를 보는데 많은 시간이 소요_된다는 단점이 있다.  </p>
</blockquote>
<h2 id="❗script를-body-가장-뒤에-작성">❗script를 body 가장 뒤에 작성</h2>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    내용
&lt;/body&gt;
&lt;script src =&quot;main.js&quot;&gt;&lt;/script&gt; 
&lt;/html&gt;</code></pre>
<p>다른 방법으로는 이와 같이 body 가장 아래에 script를 배치 하는것이다. 이러한 경우 <em><strong>Html parsing을 다 하고</strong> 스크립트를 서버에서 <strong>받고(fetcing js) 실행(executing js)</strong>하게 된다.</em> </p>
<blockquote>
<p>😊 사용자들은 페이지 컨텐츠를 미리 볼 수 있다. </p>
</blockquote>
<blockquote>
<p>🤨 다만, 웹사이트가 자바스크립트에 의존적인 경우 정상적인 페이지를 보기까지 시간이 걸린다.</p>
</blockquote>
<h2 id="❗script를-head-안에-작성-근데-이제-async-속성을-곁들인">❗script를 head 안에 작성 근데 이제 async 속성을 곁들인...</h2>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Document&lt;/title&gt;
    &lt;script async src =&quot;main.js&quot;&gt;&lt;/script&gt; 
&lt;/head&gt;
&lt;body&gt;
    내용
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>또 다른 방법으로는 head 안에 script를 이용하되 <strong>async라는 속성값</strong>을 이용하는 것이다. async는 Boolean 타입이기 때문에 선언하면 true로 설정이 된다.
이를 사용하면 브라우저가 html을 parsing 하다가 script async 를 발견하면 <em>스크립트를 서버에서 <strong>받는 작업과(fetcing js) parsing 작업을 동시에(병렬로)</strong> 한다.</em> 그러다가 <em>스크립트를 다 받으면 이를 <strong>실행(executing js)하고 나머지 html을 parsing</strong> 한다.</em> </p>
<blockquote>
<p>😊 body 끝에 script를 사용하는 것보다 _내려받는 시간을 절약_할 수 있다. </p>
</blockquote>
<blockquote>
<p>🤨 html parsing이 끝나기 전에 자바스크립트가 실행되기 때문에 <em>자바스크립트가 DOM 요소를 조작하려고 할 때 우리가 원하는 html 요소가 아직 정의 안 된 상태일 수 있다.</em></p>
</blockquote>
<h2 id="❗script를-head-안에-작성-근데-이제-defer-속성을-곁들인">❗script를 head 안에 작성 근데 이제 defer 속성을 곁들인...</h2>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Document&lt;/title&gt;
    &lt;script defer src =&quot;main.js&quot;&gt;&lt;/script&gt; 
&lt;/head&gt;
&lt;body&gt;
    내용
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>html parsing을 하다가 defer를 만나면 다운로드(fetcing js)명령을 시킵니다. html parsing 작업을 끝까지 하고 마지막에 다운로드 된 자바스크립트 파일을 실행(executing js)한다. </p>
<blockquote>
<p>😊 html parsing을 끝내서 사용자에게 페이지를 보여준 다음 자바스크립트를 실행하기 때문에 브라우저를 보는데 문제 발생이 줄어든다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트(JavaScript) 톺아보기]]></title>
            <link>https://velog.io/@2-seulgi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8JavaScript-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@2-seulgi/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8JavaScript-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 28 Feb 2022 09:23:22 GMT</pubDate>
            <description><![CDATA[<h1 id="자바스크립트의-시작-😎">자바스크립트의 시작 😎</h1>
<p>코딩 알못 시절 <del>지금도 알못</del> 자바랑 자바스크립트가 같은 언어에서 기반한 언어로 착각한 적이 있다. 자바스크립트의 초기 이름은 LiveScript 하지만, 당시 자바의 인기에 살짝 편승하고자 이름을 JavaScript로 변경하며 시작된다. </p>
<blockquote>
<p>자바스크립트는 복잡한 무언가를 웹페이지에 적용할 수 있게 하는 스크립트 혹은 프로그래밍 언어입니다. HTML, CSS가 결합되고 웹페이지 상에 올려진 후, 브라우저의 자바스크립트 엔진에 의해 실행됩니다. 
출처 : MDN Web Docs</p>
</blockquote>
<p>그러나 이러한 저러한 이유로 다양한 브라우저에서 JavaScript를 사용하는 개발자들의 고통은 계속 되었고, 이를 극복하기 위한 jQuery등의 라이브러리가 등장하게 된다. </p>
<h2 id="제이쿼리jquery란">제이쿼리(jQuery)란</h2>
<blockquote>
<p>제이쿼리(jQuery)는 웹사이트에 자바스크립트를 쉽게 활용할 수 있도록 도와주는 오픈소스 기반의 자바스크립트 라이브러리이다. </p>
</blockquote>
<p>자바스크립트 공부를 시작하면 jQuery에 대해 한번은 들어보게 된다. 제이쿼리는 이제는 역사의 저편으로 사라지는 인터넷 익스플로러가 전세계 웹브라우저 시장을 독과점 하던시절 필수적으로 사용되던 라이브러리이다. 그러나 강력한 엔진이 탑재된 크롬(Chrome)이 등장한 이후 제이쿼리와 같은 라이브러리를 사용하지 않고도 웹 애플리케이션 구현이 편리해졌고, 이후 의존도가 감소하는 추세이다. </p>
<h2 id="nodejs는-왜-사용할까🧐">Node.js는 왜 사용할까?🧐</h2>
<p>자바스크립트는 이름에서 알 수 있듯 독립적인 언어가 아니라 특정한 프로그램 안에서 동작하기 때문에 웹 브라우저 프로그램안에서만 동작을 한다. 이것이 Node.js가 나온 이유이다. </p>
<blockquote>
<p>Node.js는 JavaScript 엔진인 크롬 V8과 비동기 이벤트 처리 라이브러리인 libuv를 결합한 플랫폼이다. 즉, JavaScript로 브라우저 밖에서 서버를 구축하는 등의 코드를 실행할 수 있게 해주는 런타임 환경이다. </p>
</blockquote>
<ul>
<li>Node.js는 자바스크립트를 사용하기 위해 만들어졌다.</li>
<li>Node.js는 스크립트 언어가 아니라 프로그램(환경)이다. </li>
<li>Node.js를 사용해서 웹 브라우저와 무관한 프로그램을 만들 수 있다. </li>
<li>가장 중요한 것은 *<em>Node.js를 이용하여 서버를 만들 수 있다는 것이다. *</em> 이전에는 서버를 Java같은 다른 언어를 사용해서 만들어야 했는데 Node.js 덕분에 *<em>한가지 언어로 전체 웹 페이지를 만들 수 있게 되었다. *</em></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[REST API]]></title>
            <link>https://velog.io/@2-seulgi/REST-API</link>
            <guid>https://velog.io/@2-seulgi/REST-API</guid>
            <pubDate>Fri, 18 Feb 2022 05:52:20 GMT</pubDate>
            <description><![CDATA[<h1 id="rest-api에-대해">REST API에 대해</h1>
<p>많이 들어봤지만 들을 때마다 걔가...그건가...? 하는 REST API에 대해 정리해보려고 한다. REST API는 정보들이 주고 받아지는 데 있어서 개발자들 사이에 널리 쓰이는 일종의 <code>형식</code>을 의미한다. </p>
<h2 id="api는-뭘까">API는 뭘까?</h2>
<p>어떤 기계가 있으면 사용자가 그 기계를 활용할 수 있도록 제어장치를 만든다. 예를들어 TV와 리모컨의 관계가 그렇다. 이런 기계와 인간 간의 소통을 돕기 위해 만든 것을 인터페이스(interface)라고 한다. </p>
<blockquote>
<p>인터페이스처럼 기계와 기계, 소프트웨어와 소프트웨어의 사이에서도 수많은 정보 요청과 교환이 이루어지고 있다. 이처럼 소프트웨어가 다른 소프트웨어로부터 <strong>지정된 형식으로 요청, 명령을 받을 수 있는 수단을 API(Application Programming Interface)</strong>라고 한다. </p>
</blockquote>
<h2 id="그렇다면-rest-api는-뭘까">그렇다면 REST API는 뭘까?</h2>
<p>요즘 많이 사용하는 배달앱에서 서버에 주문을 넣는 등 이런 서비스들에서 많이 사용되는 것이 바로 REST라는 형식의 API이다. REST API의 가장 큰 특징은 각 요청이 어떤 동작이나 정보를 요구하는지 그 요청의 모습 자체로 추론 가능하다는 것이다. *<em>REST API는 요청을 보낸 주소만으로 대략 뭘 하는 요청인지 파악이 가능해진다. *</em></p>
<blockquote>
<p>REST API란, HTTP 요청을 보낼 때 어떤 URI에 어떤 메소드를 사용할지 개발자들 사이에 널리 지켜지는 약속이다. </p>
</blockquote>
<h2 id="http-규약과-rest-api">HTTP 규약과 REST API</h2>
<p>서버에 REST API로 요청을 보낼 때는 HTTP(<strong>H</strong>yper<strong>T</strong>ext <strong>T</strong>ransfer <strong>P</strong>rotocol) 규약에 따라 신호를 전송한다. HTTP로 요청을 보낼 때도 여러 메소드가 있다. REST API서는 크게 4개 혹은 5개의 메소드를 사용한다.</p>
<ul>
<li>GET : 데이터를 Read. 조회하는데 사용한다.</li>
<li>POST : Create. 즉, 새로운 정보를 body에 실어보내 정보를 추가하는데 사용한다. </li>
<li>DELETE : 정보를 삭제할 때 사용한다. </li>
<li>PUT : 변경, 업데이트 될 새 정보를 Body에 실어보낸다. Put은 정보를 통으로 바꿀때 사용한다. </li>
<li>PATCH : 변경, 업데이트 될 새 정보를 Body에 실어보낸다. Patch는 정보 중 일부를 특정 방식으로 변경할 때 사용한다. 
여기서 POST PUT PATCH는 BODY에 정보를 GET과 DELETE와 달리 많이, 비교적 안전하게 감춰서 보낼 수 있다. </li>
</ul>
<h2 id="좋은-rest-api-설계">좋은 REST API 설계</h2>
<ul>
<li>URI는 정보의 자원을 포함한다. </li>
<li>자원에 대한 행위는 HTTP Method로 표현한다. <h4 id="1-동사는-사용하지-않는다">1. 동사는 사용하지 않는다.</h4>
아래는 좋지 않은 예시이다. <pre><code>http://restapi.example.com/createrecipes
http://restapi.example.com/updaterecipes/Kimchi</code></pre>여기서 create, update는 삭제하고 명사만 가지는게 좋다. 여기서 복수형을 사용한 recipes는 <code>컬렉션</code> kimchi는 DB에서 <code>고유 식별자(unique identifier)</code>가 된다. </li>
</ul>
<h4 id="2-http-method를-활용한다">2. HTTP method를 활용한다.</h4>
<p>위에서 설명했듯 get을 통해 정보를 읽어오고, post를 통해 정보를 생성하고, put을 통해 수정하고 delete를 통해 삭제한다.</p>
<pre><code>/recipes로 GET요청을 보내면 레시피의 리스트를 가져온다. 
http://restapi.example.com/recipes</code></pre><pre><code>/recipes로 POST요청을 보내면 레시피를 생성한다. 
http://restapi.example.com/recipes</code></pre><pre><code>/recipes/kimchi 로 get 요청을 보내면 김치레시피에 대한 상세 정보를 얻는다.
http://restapi.example.com/recipes/kimchi</code></pre><pre><code>/recipes/kimchi 로 PUT/DELETE 요청을 보내면 해당 정보를 편집하거나 삭제한다. 
http://restapi.example.com/recipes/kimchi</code></pre><p>면접에서 많이 물어보는 내용이라 조금 더 공부하고 정리할예정. 
RestFul api design guidelines 등 조금 더 찾아볼 것-&gt; 모르겠소. 다시 정리 예정.....ㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋은 객체 지향 프로그래밍과 스프링(Spring)]]></title>
            <link>https://velog.io/@2-seulgi/%EC%A2%8B%EC%9D%80-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@2-seulgi/%EC%A2%8B%EC%9D%80-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Thu, 17 Feb 2022 06:12:28 GMT</pubDate>
            <description><![CDATA[<h1 id="객체-지향-프로그래밍이란">객체 지향 프로그래밍이란?</h1>
<blockquote>
<p>객체 지향 프로그래밍은 컴퓨터 프로그램을 <strong>객체의 모임</strong>으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. 객체 지향 프로그래밍은 프로그램을 <strong>유연</strong>하고 <strong>변경이 용이</strong>하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다. 
※ 출처 : 위키</p>
</blockquote>
<h3 id="유연하고-변경이-용이하다🤔">유연하고, 변경이 용이하다🤔?</h3>
<p>안 되면 코드를 통으로 뒤엎는 초보자 입장에선 확 와닿지 않는다. 간단히 말하자면 객체 지향이 추구하는 유연하고, 변경이 용이함이란, 레고 블럭 조립하듯, 컴퓨터 부품을 갈아끼우듯, <strong>컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법</strong>을 말한다. 즉 객체 지향의 핵심은 <code>다형성</code>에 있다. </p>
<h2 id="다형성이란">다형성이란?</h2>
<p>설명을 위해 <code>역할(인터페이스)</code>과 <code>구현(구현 객체)</code>으로 세상을 구분해 보자. 예를 들어 운전자는 자동차를 바꿔도 운전을 할 수 있다. 자동차가 디젤에서 전기차로 바뀌어도 운전자에게 영향을 주지 않는다. 즉, 운전자는 자동차 인터페이스(역할)만 알고 있으면 구현체가 바뀌어도 영향을 받지 않는다. 이처럼 다형성은 클라이언트에 영향을 주지 않고 새로운 기능을 제공 가능하게한다. </p>
<h3 id="다형성의-장점">다형성의 장점</h3>
<ul>
<li>클라이언트는 대상의 역할(인터페이스)만 알면 된다.</li>
<li>클라이언트는 구현 대상의 내부 구조를 몰라도 된다. </li>
<li>클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.</li>
<li>클라이언트트 구현 대상 자체를 변경해도 영향을 받지 않는다. </li>
</ul>
<p>우리는 자바 언어를 사용함에 있어서도 다형성을 활용하여 객체를 설계할 때 역할과 구현을 명확히 분리하고, 객체 설계시 <strong>역할(인터페이스)을 먼저 부여</strong>하고, 그 역할을 수행하는 구현 객체를 만들수 있다.</p>
<h3 id="다형성의-본질">다형성의 본질</h3>
<ul>
<li>인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경할 수 있다. </li>
<li>다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야한다. </li>
<li>클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다. </li>
</ul>
<h2 id="스프링과-객체-지향">스프링과 객체 지향</h2>
<ul>
<li>스프링은 <strong>다형성을 극대화</strong>해서 이용할 수 있게 도와준다. </li>
<li>스프링에서 말하는 <strong>제어의 역전, 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원</strong>한다. </li>
<li>스프링을 사용하면 <strong>구현을 편리하게 변경</strong>할 수 있다. </li>
</ul>
<p>스프링과 객체 지향 설계에 대해서 제대로 이해하기 위해서는 다형성 말고 SOLID(객체 지향 설계의 5가지 원칙)을 알아야한다. <del>또...?</del> 면접에도 나오는 질문이라고 하니 잘 기억해두자. </p>
<h2 id="좋은-객체-지향-설계의-5가지-원칙solid">좋은 객체 지향 설계의 5가지 원칙(SOLID)</h2>
<h3 id="1-srp-단일-책임-원칙-single-responsibility-principle">1. SRP 단일 책임 원칙 (Single responsibility principle)</h3>
<blockquote>
<p><strong>한개의 클래스는 하나의 책임</strong>만 가져야 한다. </p>
</blockquote>
<p>여기서 하나의 책임은 의미가 좀 모호하다. 책임은 클 수도 있고, 작을 수도 있으므로 문맥과 상황에 맞게 파악해야 한다. 책임 판단의 <strong>중요한 기준은 변경</strong>이다. 변경이 있을 때 파급 효과가 적을수록 단일 책임 원칙이 잘 지켜진 것이다.</p>
<h3 id="2-ocp-개방-폐쇄-원칙-openclosed-principle">2. OCP 개방-폐쇄 원칙 (Open/Closed principle)</h3>
<blockquote>
<p>소프트웨어 요소는 <strong>확장에는 열려</strong> 있으나 <strong>변경에는 닫혀</strong> 있어야 한다. </p>
</blockquote>
<p><img src="https://pbs.twimg.com/media/CZd_Gi9UUAAf7b8.jpg" alt="엘모 폭발">🤯🤯🤯🤯🤯🤯🤯?????솔직히 처음엔 코드의 변경 없이 확장을 하라는 게 무슨 말인지 이해가 안 갔다. 조금 더 자세히 풀어쓰자면 OPC는 다형성을 활용하는 원칙이다. <strong>역할과 구현의 분리</strong>를 하여, 인터페이스를 구현한 <strong>새로운 클래스를 하나 만들어서 새로운 기능을 구현</strong>할 수 있다. 그러나 아래 코드를 보면 문제점이 보인다. </p>
<h4 id="🤔ocp-개방-폐쇄-원칙의-문제점">🤔OCP 개방-폐쇄 원칙의 문제점</h4>
<pre><code class="language-java">public class MemberService {
    private MemberRepository memberRepository = new MemoryMemberRepository(); // 기존코드
    }</code></pre>
<pre><code class="language-java">public class MemberService {
    private MemberRepository memberRepository = new JdbcMemberRepository(); // 변경코드
    }</code></pre>
<p>이처럼 구현 객체를 변경하기 위해 클라이언트 코드(MemberService)를 변경해야한다. 결국 다형성을 사용했지만 코드의 변경이 생기는 것(OPC원칙이 지켜지지 않음)을 확인 할 수 있다. 이런 문제를 해결하기 위해 <strong>객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요</strong>해진다. 이 역할을 <code>스프링 컨테이너</code>가 해준다.</p>
<h3 id="3-lsp-리스코프-치환-원칙liskov-substitution-principle">3. LSP 리스코프 치환 원칙(Liskov substitution principle)</h3>
<blockquote>
<p>프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. </p>
</blockquote>
<p>다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다. 예를들어 자동차 인터페이스의 브레이크는 멈추는 기능이다. 앞으로 가게 구현해도 컴파일 오류는 나지 않지만 LSP위반이 된다. </p>
<h3 id="4-isp-인터페이스-분리-원칙interface-segregation-principle">4. ISP 인터페이스 분리 원칙(Interface segregation principle)</h3>
<blockquote>
<p>특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. </p>
</blockquote>
<p>예를 들어 핸드폰 인터페이스를 사용 인터페이스와 수리 인터페이스로 분리한다. 이러면 클라이언트도 핸드폰 사용자 클라이언트와, 핸드폰 수리 클라이언트로 분리할 수 있다. 이렇게 인터페이스를 분리하면 수리 인터페이스가 변해도 핸드폰 사용자 클라이언트에 영향을 주지 않아서 <strong>인터페이스가 명확해지고, 대체 가능성이 커진다.</strong> </p>
<h3 id="5-dip-의존관계-역전-원칙dependency-inversion-principle">5. DIP 의존관계 역전 원칙(Dependency inversion principle)</h3>
<blockquote>
<p>프로그래머는 <strong>추상화에 의존해야하고 구체화에 의존하면 안된다.</strong> 의존성 주입을 따르는 방법 중 하나이다.</p>
</blockquote>
<p>이는 클라이언트 코드가 <strong>구현 클래스에 의존하지 않고, 인터페이스에 의존해야한다</strong>는 의미이다. 예를 들어 사용자(클라이언트)는 역할(Role, ex. 자동차)에 의존해야하지 구현체(ex. k5, 테슬라 모델3, 아이오닉 등)에 의존해서는 안된다. MemberService가 MemberRepository만 바라보고, MemoryMemberRepository나 JdbcMemberRepository는 몰라야한다. 이런식으로 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다. 그러나 OCP에서 설명한 MemberService는 MemberService 클라이언트가 구현 클래스를 직접 선택 가능하다. 즉, MemberRepository 인터페이스만 의존하지 않고, 구현 클래스도 동시에 의존한다. </p>
<pre><code class="language-java">//MemberService는 MemberRepository와 MemoryMemberRepository에도 의존한다. 
public class MemberService {
    private MemberRepository memberRepository = new MemoryMemberRepository(); 
    }</code></pre>
<h3 id="정리">정리</h3>
<ul>
<li>객체 지향의 핵심은 <code>다형성</code>이다. </li>
<li>그러나 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다. 즉 부품을 갈아 끼우듯 개발할 수 없다. </li>
<li>결과적으로 *<em>다형성 만으로는 OCP, DIP를 지킬 수 없다. *</em></li>
</ul>
<h2 id="스프링과-객체지향">스프링과 객체지향</h2>
<blockquote>
<p>스프링은 의존성을 주입하는 <strong>DI(Dependency Injection)와 DI 컨테이너</strong>로 다형성과 OCP, DIP를 가능하게 지원한다. 이를 통해 클라이언트의 코드 변경 없이 기능 확장이 가능하다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링(Spring)의 시작]]></title>
            <link>https://velog.io/@2-seulgi/%EC%8A%A4%ED%94%84%EB%A7%81Spring%EC%9D%98-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@2-seulgi/%EC%8A%A4%ED%94%84%EB%A7%81Spring%EC%9D%98-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Tue, 15 Feb 2022 18:24:28 GMT</pubDate>
            <description><![CDATA[<h1 id="스프링의-시작">스프링의 시작</h1>
<blockquote>
<p>2002년 로드존슨이 EJB의 문제점을 지적하면서 출간한 책이 등장한다. 이책에 실린 30,000개의 코드가 <strong>스프링 핵심 개념과 기반 코드</strong>가 되었다. 이것을 본 Juergen Hoeller(유겐 휠러)와 Yann Caroff(얀 카로프)가 로드존슨에게 오픈소스 프로젝트를 제안하며 스프링이 시작된다. </p>
</blockquote>
<p>이처럼 EJB의 추운 겨울에서 많은 개발자가 고통받던 중 혜성처럼 등장한 게 Spring이다. 개발자들의 겨울이 끝나고 봄이 왔다는 의미로 Spring이라는 이름을 붙였다고 한다.
<img src="https://images.velog.io/images/2-seulgi/post/509cd593-8605-4c1a-a8e0-612501ba4d70/iHH12.png" alt="오류"><del>...근데 왜 나는 한겨울?🥶</del></p>
<h1 id="스프링이란">스프링이란?</h1>
<p>스프링 생태계에는 스프링 프레임워크, 스프링 부트, 스프링 데이터, 스프링 세션, 스프링 시큐리티, 스프링 Rest Docs, 스프링 배치, 스프링 클라우드등이 있다. 이처럼 스프링은 어떤 하나가 아니라 <strong>여려가지 기술들의 종합체</strong>이다. 
더 알고 싶다면 <a href="https://spring.io/projects">Spring 홈페이지</a> 이곳에서 더 많은 기술을 확인할 수 있다.</p>
<h2 id="스프링-프레임워크spring-framework">스프링 프레임워크(Spring Framework)</h2>
<blockquote>
<p>자바 플랫폼을 위한 오픈소스 애플리케이션 프레임워크. 엔터프라이즈급 애플리케이션을 개발하기 위한 모든 기능을 포괄적으로 제공한다.</p>
<blockquote>
<p>🧐_엔터프라이즈급 애플리케이션 개발이란? _
대규모 데이터 처리와 트랜잭션이 동시에 여러 사용자로 부터 행해지는 매우 큰 규모의 환경을 말한다. </p>
</blockquote>
</blockquote>
<ul>
<li>핵심 기술 : 스프링 DI 컨테이너, 이벤트, 유효성 검사, 데이터 바인딩, 유형 변환, AOP 등</li>
<li>테스팅 : 모의 객체, TestContext 프레임워크, Spring MVC 테스트 등 스프링 기반 테스트 지원 </li>
<li>데이터 접근 : 트랜잭션, DAO 지원, JDBC, ORM, XML 지원</li>
<li>웹 기술 : 스프링MVC, 스프링 WebFlux</li>
<li>통합 : 이메일, 캐시, 스케쥴링, 원격접근</li>
<li>언어 : Kotlin, Groovy, 동적 언어.</li>
</ul>
<h2 id="스프링-부트spring-boot">스프링 부트(Spring Boot)</h2>
<blockquote>
<p>스프링 프레임워크를 편리하게 사용하기 위해 지원하는 도구. 최근에는 모든 실무 프로젝트에서 기본으로 사용한다. </p>
</blockquote>
<ul>
<li>단독으로 실행 가능한 <strong>독립형 Spring 애플리케이션을 쉽게 생성</strong>한다. 즉, Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버 없이 사용가능하다.</li>
<li>빌드 구성을 쉽게하기 위해 독자적인 <strong>&#39;stater&#39; 종속성을 제공</strong>한다. 따라서 라이브러리를 편하게 가져올 수 있다. </li>
<li>Spring과 서드파티 라이브러리를 <strong>자동으로</strong> 구성한다. 따라서 외부 라이브러리 버전에 대해 크게 고민하지 않아도 된다.</li>
<li>메트릭, 상태 확인 및 외부 구성과 같은 프로덕션 준비 기능을 제공한다. 운영환경에서 모니터링을 스프링 부트가 어느정도 지원한다. </li>
<li>관례에 의한 간결한 설정이 가능하다. </li>
</ul>
<h3 id="🤔스프링이라는-단어에-대한-고찰">🤔스프링이라는 단어에 대한 고찰</h3>
<p><img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQw7ScZetzkY29itbWsv1bXb8ozdnxvQ95wvA&usqp=CAU" alt="고찰">스프링에 대해 구글링을 열심히 하다 보면 단어에 대한 혼란이 생기기 시작한다. 스프링 프레임워크를 말하는 건지, 스프링 부트를 말하는 건지 뭐가 뭔지🤯. 이 애매모호함에 대한 정답은 없다. 즉, 스프링이라는 단어는 <strong>문맥에 따라</strong> 다르게 사용된다. 그러나 크게 아래 3개로 범위를 좁힐 수 있을 거 같다.</p>
<ul>
<li>스프링 DI 컨테이너 기술</li>
<li>스프링 프레임워크</li>
<li>스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계 전체</li>
</ul>
<h2 id="스프링의-핵심">스프링의 핵심</h2>
<p>스프링의 핵심은 무엇일까? 위에서 언급한 스프링의 특징보다 중요한 핵심이 있다. </p>
<blockquote>
<p>스프링은 자바 언어 기반의 프레임 워크이다. 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는데 용이하다. 즉, <strong>스프링은 좋은 객체 지향 애플리케이션을 만들 수 있게 도와주는 프레임워크</strong>이다. </p>
</blockquote>
<br>
<br>
<br>
<br>
<br>

<p><img src="https://images.velog.io/images/2-seulgi/post/726f0f90-3383-43b8-8c9b-e29224750256/img.png" alt="">강의 내용 + 스프링 홈페이지 + 구글링을 통해 스프링에 대해 간단히 정리해보았다. 다음 강의는 객체지향 프로그래밍에 대한 내용이다. 잘 듣고 정리해 봐야지🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린(Kotlin)과 자바(Java)]]></title>
            <link>https://velog.io/@2-seulgi/%EC%BD%94%ED%8B%80%EB%A6%B0Kotlin%EA%B3%BC-%EC%9E%90%EB%B0%94Java</link>
            <guid>https://velog.io/@2-seulgi/%EC%BD%94%ED%8B%80%EB%A6%B0Kotlin%EA%B3%BC-%EC%9E%90%EB%B0%94Java</guid>
            <pubDate>Sun, 13 Feb 2022 16:47:53 GMT</pubDate>
            <description><![CDATA[<p>나는 crud 게시판도 눈물 흘리며 만드는 쪼렙이지만, 이미 현업에서 일하는 멋진 친구에게 요즘은 무슨 언어가 좋아? 라고 물어본적이 있다.
프론트냐 백이냐 웹이냐 앱이냐 다 다르겠지만 일단 백엔드를 배운다고 하니 친구가 천천히 배워보라며 말해준 언어들 중 하나가 <code>코틀린Kotlin</code>이다. 궁금하니까 자바도 잘 못하는 초보자 입장에서 <del>깊이 없는</del> 글을 써보려고 한다.</p>
<h1 id="🤔코틀린이-뭔데">🤔코틀린이 뭔데?</h1>
<blockquote>
<p>인텔리제이의 개발사 JetBrains에서 공개한 JVM기반의 언어. Java와 유사하지만 더 간결하고, 호환도 100% 된다. 그래서 Java를 활용하는 거의 모든곳에 코틀린을 활용할 수 있다고 한다. </p>
</blockquote>
<p>익숙한 젯브레인 만나서 반갑고...! 아무튼 코틀린을 얄려준 친구가 <strong>코틀린 한번 쓰면 자바로 못돌아간다</strong>는 <del>굉장히 유혹적인말...</del>소리를 해서 뭐가 더 좋다는건지 검색을 해봤다. </p>
<h2 id="1-자바는-너무-딱딱해-근데-난-아님">1. 자바는 너무 딱딱해. 근데 난 아님.</h2>
<p>예를 들어 자바의 경우 정적 타입 언어이다. 즉, 코드를 작성할때 직접 타입을 지정해줘야 한다. 
그럼 코틀린은 동적이라서 좋은거냐? 하면 그건 아니고🙄... 코틀린도 정적 타입 언어이다. 다만 코틀린은 문맥을 파악해 <strong>타입을 추론</strong>할 수 있기 때문에 <strong>타입 선언을 생략</strong>할 수 있다는 장점이 있다. </p>
<pre><code class="language-kotlin">var x: Int = 10 
// Int를 생략할 수 있다. 
var x = 10</code></pre>
<h2 id="2-nullpointerexceptionnpe-그게-뭔데">2. NullPointerException(NPE) 그게 뭔데?</h2>
<p>자바로 개발을 하는 사람들에게 NullPointerException은 숙명 운명 데스티니... 아무튼 그런 오류이다. 놀랍게도 코틀린은 이러한 NullPointerException오류에서 어느정도 자유로운듯 하다. Null-safety를 언어차원에서 지원하기 때문이라는데... 대충 알아보자.</p>
<blockquote>
<p>Null-safety는 null 안전성을 도와주는 개념이다. 코틀린은 널이 될 수 있는 타입과 널이 될 수 없는 타입(Nullable types and Non-Null Types)을 구분해서 NPE 발생 가능성이 있는 연산 및 코드를 사전에 방지한다. </p>
</blockquote>
<p><img src="http://file3.instiz.net/data/cached_img/upload/2020/04/27/17/2fca1ebba197a2af4d195377f267eea6.jpg" alt="흠">...쓰면서도 뭔말인지 모르겠다😥. 처음 보는 말이 있어서 조금 더 구체적으로 정리해보기로... <br></p>
<h3 id="nullable-types과-non-null-types">Nullable types과 Non-Null Types</h3>
<p><code>Nullable types</code>은 <code>null</code>이 담길 수 있는 타입을 의미한다. 자료형에 <code>?</code> 를 붙여 명시한다. 반면 <code>Non-Null Types</code>은 <code>null</code>을 허용하지 않는다. </p>
<pre><code class="language-kotlin">var nullable: String? = &quot;abc&quot;
nullable = null // 컴파일 성공
var nonNullable: String = &quot;abc&quot;
nonNullable  = null // 컴파일 에러</code></pre>
<p>*<em>이처럼 코틀린은 Non-Null에 null값이 들어가려고 하면 컴파일 에러가 발생한다. *</em> 이를 통해 코틀린은 NullPointerException에서 <em>어느정도</em> 자유로울 수 있다. 
그러나 아주 아쉽게도 자바 라이브러리를 사용하는 경우, 자바는 Non-Null 타입이 없기 때문에 Nullable 타입으로 리턴되서 NPE가 발생 하는 경우가 있다고 한다. 
<br><br><br>이렇게 정말 초 심플하게 코틀린에 대해 훑어봤다. 다 정리하진 못했지만 짧게 구글링해서 알아본 코틀린은 실용적이고, 간결하며, 그렇다고 안전성을 포기하지도 않고, 요즘 대세인 함수형 프로그래밍을 사용함에 있어서도 유리한 멋진 언어로 보인다😎. 특히 구글에서도 안드로이드 공식 개발 언어로 코틀린을 지목한 만큼 미래가 창창한 언어라는 것엔 아무도 부정하지 못할거 같다는 생각이 들었다.</p>
<p>나도 언젠간 제대로 공부를 할 예정이지만 <del>솔직히 한국에서는 자바가 10년은 더 해먹을거 같고...🙄</del> 아직은 학원에서 배우는 자바, 스프링부트에 충실할 예정이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[마크다운(markdown)이 뭔데 그거 어떻게 하는건데]]></title>
            <link>https://velog.io/@2-seulgi/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4markdown%EC%9D%B4-%EB%AD%94%EB%8D%B0-%EA%B7%B8%EA%B1%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%8A%94%EA%B1%B4%EB%8D%B0</link>
            <guid>https://velog.io/@2-seulgi/%EB%A7%88%ED%81%AC%EB%8B%A4%EC%9A%B4markdown%EC%9D%B4-%EB%AD%94%EB%8D%B0-%EA%B7%B8%EA%B1%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%8A%94%EA%B1%B4%EB%8D%B0</guid>
            <pubDate>Fri, 11 Feb 2022 15:06:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://search.pstatic.net/common/?src=http%3A%2F%2Fblogfiles.naver.net%2FMjAyMDAzMTlfMTI3%2FMDAxNTg0NjIwODUwMDcy.mhbhyHsALaGx0BxJdHHCKsg8Yv27-_DrXffVKDL2UtIg.NB1rk-SJL2_s90Hx9STV9WAAD_eSwmGU_jw0DO8I5uUg.JPEG.o_o_o3o_hb%2Foutput_2161863776.jpg&type=sc960_832" alt="마크다운그거어떻게하는건데"><del>_<font color = "grey"><font size = 2><p align = center>&quot;마크다운 써&quot;
&quot;그게 뭔데&quot;
&quot;마크다운 쓰라고&quot; 
&quot;그거 어떻게 하는건데&quot;</p></font></font>_</del></p>
<p>벨로그를 하려면 알아야 한다는 마크다운에 대해 <del>나만 보기 위해</del> 정리해보려고 한다. 어차피 velog 하면서 계속 사용할테니 <del>그때그때배워서</del> 천천히 추가할 예정!</p>
<h2 id="마크다운이란">마크다운이란</h2>
<blockquote>
<p>마크다운(markdown)은 일반 텍스트 기반의 경량 마크업 언어다. HTML과 리치 텍스트(RTF) 등 서식 문서로 쉽게 변환되기 때문에 응용 소프트웨어와 함께 배포되는 README 파일이나 온라인 게시물 등에 많이 사용된다.
-위키백과-</p>
</blockquote>
<h2 id="📒제목-사용하기">📒제목 사용하기</h2>
<p>Html에서 사용하는 <code>h1</code>부터 <code>h6</code>을 이용할 수 있다.</p>
<p>✍️ 마크다운 작성 </p>
<pre><code># h1 사이즈 제목
## h2 사이즈 제목
### h3 사이즈 제목
#### h4 사이즈 제목
##### h5 사이즈 제목
###### h6 사이즈 제목</code></pre><p>👉 결과</p>
<h1 id="h1-사이즈-제목">h1 사이즈 제목</h1>
<h2 id="h2-사이즈-제목">h2 사이즈 제목</h2>
<h3 id="h3-사이즈-제목">h3 사이즈 제목</h3>
<h4 id="h4-사이즈-제목">h4 사이즈 제목</h4>
<h5 id="h5-사이즈-제목">h5 사이즈 제목</h5>
<h6 id="h6-사이즈-제목">h6 사이즈 제목</h6>
<h3 id="🧐-h1랑-h2는-이런-방법도-있다">🧐 h1랑 h2는 이런 방법도 있다!</h3>
<p><code>==</code>은 h1, <code>--</code>h2를 사용해서 <code>#</code>를 대체할 수 있다. =,- 갯수는 중요하지 않다. 
✍️ 마크다운 작성 </p>
<pre><code>h1
===
h2
-</code></pre><p>👉 결과
h1
====
h2
--
<img src="https://d3kxs6kpbh59hp.cloudfront.net/community/COMMUNITY/9ac2c4d9475042ed8252d229db1d826b/193351d2f8894ac29b8eb6757de09d61_1610085216.gif" alt="흠터레스팅">생각해보니까 제목 보다 이미지 넣는 방법을 궁금해 할거 같다. <del>왜냐하면 내가 그랬...</del></p>
<h2 id="📒이미지-링크-사용하기">📒이미지 링크 사용하기</h2>
<p>이미지 링크는 이렇게 사용한다. 
✍️ 마크다운 작성 </p>
<pre><code>![이미지설명](이미지링크)
![흠터레스팅](https://d3kxs6kpbh59hp.cloudfront.net/community/COMMUNITY/9ac2c4d9475042ed8252d229db1d826b/193351d2f8894ac29b8eb6757de09d61_1610085216.gif)</code></pre><p>👉 결과는 위에서 확인.
<br>
✍️ 마크다운 작성 </p>
<pre><code>[![이미지 설명](이미지링크)](연결하려는 URL)
[![고양이](https://cdn.pixabay.com/photo/2017/02/20/18/03/cat-2083492_960_720.jpg)](https://pixabay.com/ko/)</code></pre><p>👉 결과. 이미지를 누르면 <a href="https://pixabay.com/ko/">https://pixabay.com/ko/</a> 로 이동한다. 
<a href="https://pixabay.com/ko/"><img src="https://cdn.pixabay.com/photo/2017/02/20/18/03/cat-2083492_960_720.jpg" alt="고양이"></a></p>
<h2 id="📒인용-사용하기">📒인용 사용하기</h2>
<p><code>&gt;</code>를 사용해서 인용을 표현할 수 있다. 중첩해서 사용도 가능하다. 
✍️ 마크다운 작성</p>
<pre><code>&gt; 인용글 작성 1단
1단입니다.
&gt;&gt; 인용글 2단
2단입니다.
&gt;&gt;&gt; 인용글 3단
3단입니다.</code></pre><p>👉 결과.</p>
<blockquote>
<p>인용글 작성 1단
1단입니다.</p>
<blockquote>
<p>인용글 2단
2단입니다.</p>
<blockquote>
<p>인용글 3단
3단입니다.</p>
</blockquote>
</blockquote>
</blockquote>
<h2 id="📒블럭-사용하기">📒블럭 사용하기</h2>
<p>코드블럭은 작성한 코드를 정리하고 강조하고 싶은 부분을 나타낸다. <code>인라인</code>과 <code>블럭 단위</code>로 구분 할 수 있다. </p>
<p>✍️ 인라인 블럭 마크다운 작성 </p>
<pre><code>`(백틱)으로 감싸준다`</code></pre><p>👉 결과
<code>인라인 블럭</code></p>
<hr>
<p>✍️ 블럭단위 마크다운 작성 </p>
<pre><code>위 아래로 `(백틱)을 3개 사용해서 감싸준다</code></pre><p>👉 결과</p>
<pre><code>결과</code></pre><hr>
<p>✍️ 코드블럭 마크다운 작성 </p>
<pre><code>```(백틱3개) + 사용할 언어 이름 
코드 내용
```(백틱3개)

```java 
public class helloWorld{
    public void main(String[] args) {
        System.out.println(&quot;Hello World&quot;);
    }
}</code></pre><p>👉 결과</p>
<pre><code class="language-java">public class helloWorld{
    public void main(String[] args) {
        System.out.println(&quot;Hello World&quot;);
    }
}</code></pre>
<table>
<thead>
<tr>
<th>사용언어</th>
<th>작성방식</th>
</tr>
</thead>
<tbody><tr>
<td>Java</td>
<td>java</td>
</tr>
<tr>
<td>Python</td>
<td>python</td>
</tr>
<tr>
<td>Kotlin</td>
<td>kotlin</td>
</tr>
<tr>
<td>HTML,XML</td>
<td>html</td>
</tr>
<tr>
<td>CSS</td>
<td>css</td>
</tr>
<tr>
<td>JavaScript</td>
<td>javascript</td>
</tr>
<tr>
<td>SQL</td>
<td>sql</td>
</tr>
<tr>
<td>🙄이 밖에도 사용 가능한 언어 목록은 더 많다. .</td>
<td></td>
</tr>
</tbody></table>
]]></description>
        </item>
    </channel>
</rss>