<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sh_38.log</title>
        <link>https://velog.io/</link>
        <description>그냥 합니다.</description>
        <lastBuildDate>Tue, 13 May 2025 14:43:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sh_38.log</title>
            <url>https://velog.velcdn.com/images/sh_38/profile/16ff00bb-62c6-460a-be14-1ab27812bbe7/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sh_38.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sh_38" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[DDD(도메인 주도 개발)]]></title>
            <link>https://velog.io/@sh_38/DDD%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@sh_38/DDD%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Tue, 13 May 2025 14:43:36 GMT</pubDate>
            <description><![CDATA[<p>DDD(Domain-Driven Design)라는 소프트웨어 개발 방법론이 있다.</p>
<p>사람들은 왜 이것에 열망하는지? 무슨 장점이 있길래? 이와 반대되는 개념은 뭐가있는지? 이런걸 좀 알아보았다.</p>
<hr>
<h3 id="ddd란---도메인-주도-개발">DDD란 - 도메인 주도 개발</h3>
<ul>
<li><ol>
<li>대규모 서비스의 복잡한 비즈니스 로직을 체계적이고 명확하게 표현하고 관리하기 위함.</li>
</ol>
</li>
</ul>
<p>-&gt; 비즈니스 로직을 관리하기 위해 &#39;도메인 모델&#39; 을 중심으로 개발을 한다고 한다.</p>
<br>

<ul>
<li><ol start="2">
<li>비즈니스 전문가와 개발자가 동일한 언어로 소통하기 위함</li>
</ol>
</li>
</ul>
<p>-&gt; 코드와 비즈니스 용어가 일치하도록 설계한다.
이때 사용하는 언어가 <strong>&#39;유비쿼터스 언어&#39;</strong> 라고 한다. (비즈니스 + 개발 공통 언어)</p>
<br>

<ul>
<li><ol start="3">
<li>도메인을 Bounded Context 로 분리하여 책임을 명확하게 한다.</li>
</ol>
</li>
</ul>
<p><code>주문관리</code> 와 <code>재고관리</code> 가 서로의 로직에 영향을 주지 않게 <strong>&#39;독립성&#39;</strong>을 보장한다.</p>
<br>

<ul>
<li><ol start="4">
<li>레이어드 아키텍쳐 ( 도메인레이어 + 애플리케이션레이어 + 인프라레이어 ) 로 구성되어서 도메인 로직을 명확하게 구분하여 관리한다.</li>
</ol>
</li>
</ul>
<p>명확하게 분리가 잘 되어있으면 부분적인 수정이 시스템에 영향을 미치지 않는다. <strong>코드 변경시 리스크 감소</strong></p>
<br>

<ul>
<li><ol start="5">
<li>단위 테스트 용이성</li>
</ol>
</li>
</ul>
<p>도메인 로직이 분리되어있기 때문에 테스트하기에 무척 용이하다고 한다.</p>
<h4 id="결국-ddd-란">결국 DDD 란</h4>
<p>도메인 (주문, 재고관리, 배송, ...)에서의 규칙을 정해놓은 부분을 도메인이라고 하고
이 도메인을 객체지향적(확장성+유지보수성)으로 개발하는 방법론이다.</p>
<hr>
<h3 id="ts란---트랜잭션-스크립트">TS란 - 트랜잭션 스크립트</h3>
<p>트랜잭션 스크립트는 DDD와 거의 반대되는 개발 방법론이다. (다른것이지 틀린건 아님)</p>
<p>절차 중심적인 설계를 한다.</p>
<p><strong>DDD와의 차이점</strong></p>
<p>TS는</p>
<ol>
<li><p>절차지향적이다.</p>
</li>
<li><p>데이터를 직접 조작하며 비즈니스 로직이 코드에 그대로 들어가있다.</p>
</li>
<li><p>코드의 흐름이 명확하게 짜여져있어서 이해하기가 쉽다.</p>
</li>
<li><p>작은 규모의 프로젝트에 적합하다.</p>
</li>
</ol>
<br>

<p>하지만 이러한 점들에는 한계점이 있는데</p>
<p>코드의 중복이 많이 생긴다는 것.</p>
<p>그리고 코드 한 부분을 수정하면 다른 부분까지 수정해야 하는 경우가 자주 생김</p>
<hr>
<p>아직은 개념적인 부분에 대해서 접근하고 있고 조금씩 넓혀나갈 생각이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 부트 테스트코드 작성하기]]></title>
            <link>https://velog.io/@sh_38/Junit5-%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sh_38/Junit5-%EB%A1%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 09 May 2025 16:47:36 GMT</pubDate>
            <description><![CDATA[<h1 id="테스트코드는-왜-작성하는거지">테스트코드는 왜 작성하는거지?</h1>
<blockquote>
<p>그냥 서비스 하나 만들었으면 Postman으로 쭉 테스트 해보면 되는거 아닌가?</p>
</blockquote>
<p>라고 생각했다.</p>
<p>하지만 테스트코드의 장점은 이 뿐 만이 아니다.</p>
<p><strong>만약 이전에 완성했던 코드를 수정했을 때, 또 다시 모든 경우를 하나하나 직접 테스트 해봐야 한다.</strong></p>
<p>Postman 테스트는 사람이 직접 하는거다 보니 실수가 생길 수 있다. 이런 경우를 대비하여
테스트코드를 작성해두고 발생할 수 있는 문제의 경우의 수를 코드로 작성해 두는 것이다.</p>
<h1 id="junit5">JUnit5</h1>
<ul>
<li>JUnit 5는 Java 애플리케이션에서 <strong>단위 테스트(Unit Test)</strong>를 수행하기 위해 사용되는 <strong>테스트 프레임워크</strong></li>
</ul>
<br>

<h3 id="모듈화-구조로-인한-유연성">모듈화 구조로 인한 유연성</h3>
<ul>
<li><p>JUnit 5는 크게 <strong>3개의 모듈</strong>로 구성됩니다:</p>
<ul>
<li><strong>JUnit Platform</strong>: 테스트 실행 및 결과 보고 (테스트 실행 환경)</li>
<li><strong>JUnit Jupiter</strong>: JUnit 5의 핵심 모듈, 실제 테스트 작성 (테스트 시 사용되는 대부분의 모듈들)
ex) <code>@Test</code>, <code>@BeforeEach</code>, <code>@AfterEach</code>, <code>@Nested</code>, <code>@DisplayName</code></li>
<li><strong>JUnit Vintage</strong>: JUnit 4와 호환성을 제공</li>
</ul>
<p>JUnit5을 통해 만들어진 테스트 환경속에서 테스트를 진행하게 된다.</p>
</li>
</ul>
<h1 id="assertj">AssertJ</h1>
<p>AssertJ를 통해 내 테스트코드를 검증하자.</p>
<p>JUnit5 에도 <code>Assertions</code> 클래스가 제공 된다. 하지만 AssertJ를 따로 추가해서 사용하는것이 더 가독성이 좋다. AssertJ는 함수형 프로그래밍 패러다임으로 만들어졌기 때문이다.</p>
<ul>
<li>JUnit5의 Assertions</li>
</ul>
<pre><code class="language-java">    @Test
    void testEquals() {
        String actual = &quot;Hello&quot;;
        String expected = &quot;Hello&quot;;
        assertEquals(expected, actual);
    }</code></pre>
<ul>
<li>AssertJ<pre><code class="language-java">  @Test
  void testEquals() {
      String actual = &quot;Hello&quot;;
      assertThat(actual).isEqualTo(&quot;Hello&quot;);
  }</code></pre>
</li>
</ul>
<p>무엇을 검사할지 명확하게 지정하고, 검사 방법까지 다음 메서드로 호출하기 때문에 차례대로 읽기가 쉽다.</p>
<br>

<h1 id="mockito">Mockito</h1>
<p>단위 테스트를 위해서 사용되는 mock 객체 라이브러리이다.
가짜 객체를 만들어주는 역할을 한다.</p>
<ol>
<li><p><strong>텅 빈 가짜 객체를 만들어 주고(의존성 주입)</strong> 테스트 할 코드를 실행하는데 문제가 없도록 만들어 준다.</p>
</li>
<li><p>mock 객체 내부의 메서드를 사용해야 한다면 정의를 해줘야 사용이 가능하다.
 (&#39;파라미터는 무엇이고&#39; 그 파라미터가 들어왔을 때 , &#39;어떤걸 반환한다 혹은 throw 한다&#39;)</p>
</li>
<li><p>해당 테스트에서 mock 객체를 만들어두고 사용되지 않는다면 이것또한 찾아준다.</p>
</li>
<li><p>메서드 호출 횟수를 검증할 수 있다.</p>
</li>
</ol>
<hr>
<pre><code class="language-java">
        @Test
        public void 정상동작의_경우() {

            // given: Mock 객체 생성, 테스트 해볼 동작을 설정
            User mockAdminUser= User.builder()
                    .id(1L)
                    .authCode(&quot;adminUser authCode&quot;)
                    .role(User.Role.ROLE_ADMIN)
                    .email(&quot;admin@google.com&quot;)
                    .type(OAuth2Type.GOOGLE)
                    .nickname(&quot;운영자&quot;)
                    .password(&quot;admin password&quot;)
                    .registerDate(LocalDateTime.now())
                    .build();

            BoardNotice mockNotice = BoardNotice.builder()
                    .id(1L)
                    .title(&quot;notice Write title&quot;)
                    .content(&quot;notice Write content&quot;)
                    .views(123)
                    .type(BoardType.NOTICE)
                    .thumbnail(&quot;thumbnail url&quot;)
                    .status(BoardStatus.PUBLISHED)
                    .user(mockAdminUser)
                    .createdAt(LocalDateTime.now())
                    .publishedAt(LocalDateTime.now())
                    .updatedAt(null)
                    .deletedAt(null)
                    .imageToken(&quot;notice image token&quot;)
                    .build();


            Long noticeId = 1L;

            BoardAdminRequest.Notice request = new BoardAdminRequest.Notice();
            request.setTitle(&quot;title&quot;);
            request.setBoardType(&quot;NOTICE&quot;);
            request.setImageSrc(List.of(&quot;img1&quot;, &quot;img2&quot;));
            request.setThumbnail(&quot;thumbnail&quot;);
            request.setContent(&quot;content&quot;);
            request.setImageToken(&quot;imageToken&quot;);

            AuthUser mockAuthUser = new AuthUser(new UserDto(mockAdminUser.getId(), mockAdminUser.getEmail(), mockAdminUser.getNickname(), mockAdminUser.getAuthCode(), mockAdminUser.getRegisterDate(), mockAdminUser.getType(), mockAdminUser.getRole()));

            when(userRepository.findById(any(Long.class))).thenReturn(Optional.of(mockAdminUser));
            when(noticeBoardRepository.save(any(BoardNotice.class))).thenAnswer(invocation -&gt;
            {
                BoardNotice boardNotice = invocation.getArgument(0);

                try {
                    Field idField = BoardNotice.class.getDeclaredField(&quot;id&quot;);
                    idField.setAccessible(true);
                    idField.set(boardNotice, noticeId);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    throw new RuntimeException(&quot;필드 수정 오류&quot;, e);
                }

                return boardNotice;
            });
            when(boardNoticeMapper.toNewNoticeId(any(Long.class)))
                    .thenReturn(new BoardAdminResponse.newNotice(mockNotice.getId()));

            // when: 실제 테스트 하고싶은 메서드
            BoardAdminResponse.newNotice result = boardAdminService.writeNotice000(request, mockAuthUser);

            // then: 테스트 메서드 동작 후 검증 단계
            assertThat(result).isNotNull();
            assertThat(result.getNoticeId()).isEqualTo(mockNotice.getId());

            verify(userRepository).findById(mockAuthUser.getUserDto().id());
            verify(boardImageService).checkAndSave(eq(request.getImageSrc()), eq(request.getImageToken()));
            verify(boardNoticeMapper).toNewNoticeId(mockNotice.getId());

        }
</code></pre>
<p>테스트 코드는 <strong>given, when, then</strong> 으로 나누어서 작성하는 방식을 따른다.</p>
<p><strong>&#39;given&#39;</strong> 에서는 테스트 환경에 필요한 <strong>사전 구성 작업</strong>이다.
여기서 mock 객체에 대한 설정들을 해주고 mock 내부 메서드를 사용한다면 채워줘야 한다.</p>
<p><strong>&#39;when&#39;</strong> 에서는 우리가 만들어놓은 가상의 환경에서 <strong>테스트 하고싶은 메서드를 실행</strong>한다.</p>
<p><strong>&#39;then&#39;</strong> 에서는 테스트 후 결과를 <strong>검증</strong>하는 단계이다.
위 코드에서는 <code>assertThat</code>(AssertJ) 이랑 <code>verify</code>(Mockito)를 통해서 검증하고 있다.</p>
<p>assertThat은 값을 직접 확인하고, verify 는 메서드 호출(몇번 호출했는지, 어떤 인자가 들어갔는지)에 대한 검증을 한다.</p>
<hr>
<p>05.15 추가)</p>
<p>테스트코드를 작성하면서 <strong><code>spy</code></strong> 객체라는것을 접하게 되었다.
(사용하지는 않았음.)</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/8abd79dd-9582-401e-baa3-4279f532b1cf/image.png" alt=""></p>
<h3 id="spy">spy</h3>
<p>java 에서 <code>primitive</code> 타입으로 <code>int</code>, <code>long</code> 이런것들이 있다.
이러한 값들을 하나의 객체로서 사용하기 위해서 우리는
<code>Wrapper</code> 클래스를 통해 각 <code>primitive</code> 값들을 감싸서 객체로 사용할 수 있게 된다.</p>
<p>여기서 마치 <code>Wrapper</code> 클래스와 비슷한 역할을 한다.
<code>spy</code>는 우리가 만든 &#39;실제 객체&#39;를 감싸서 &#39;mock 객체&#39; 로 만들어 주는 역할을 한다.
이러한 짓을 왜하는거지? 싶지만</p>
<p>실제 객체의 대부분의 기능을 사용하고 싶은데
<strong>하나의 기능만 mocking 해서 사용하고 싶을때</strong></p>
<p>즉
실제 객체의 <strong>부분적인 mocking 이 필요할 때</strong> 사용한다.</p>
<hr>
<p>내가 <code>spy</code>를 사용하게 된 경위는
아직 테스트코드에 대해서 익숙하지 않아서였다.</p>
<p>mockito 에서 제공하는 <code>verify</code> 라는 메서드가 있다.</p>
<p>이는 &#39;mock 객체&#39;의 특정 메서드가</p>
<ol>
<li>호출 되었는지? </li>
<li>몇 회 호출 되었는지? </li>
<li>호출 시 특정 파라미터로 호출 되었는지?</li>
</ol>
<p>를 확인할 수 있다.</p>
<br>

<h3 id="왜-안쓰는거야">왜 안쓰는거야</h3>
<p><code>verify</code> 메서드를 실제 객체를 대상으로 사용하려고 했고 에러가 발생했다.</p>
<p>그래서 이를 해결하기 위해 단순히 <code>spy</code>로 감싸서 사용했지만</p>
<p>엔티티 즉 POJO 객체(순수한 도메인 로직)에는 <code>spy</code>를 사용하지 않는것이 원칙이다.</p>
<p>굳이 해당 객체를 <code>spy</code>로 감싸지 않고도 검증할 수 있기 때문이다.</p>
<p>-&gt; 무조건! 꼭! 내가 <code>verify</code>를 통해서 메서드 호출 횟수와, 파라미터를 검증해야겠다~ 고 한다면
<code>spy</code>로 감싸서 확인할 수 있다.</p>
<br>

<p>하지만 테스트 코드에서 중요하다고 생각하는건 <strong>쓸데없는 모킹, 쓸데없는 검증</strong> 은 지양해야한다.</p>
<p>안그래도 많은 mocking 과정이 필요한데 <strong>검증하지 않아도 되는 검증까지 해버리는 코드</strong>가 들어간다면 코드를 읽는데도 어려울게 당연하기 때문</p>
<p><u>결론</u>: <strong>내가 이 코드에서 정확하게 이것을 검증해야 한다.</strong> 라는 것을 명확히 하고 테스트 코드를 작성할 필요가 있다.</p>
<hr>
<p>테스트코드를 작성안하면 불안에 떠는 개발자들이 많다고 하더라...</p>
<p>나는 여태껏 테스트코드를 많이 안써봤지만 앞으로 꼼꼼하게 작성해봐야겠다.</p>
<p>끝!</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/44a5b726-a02a-46a0-8403-70ccf55930fd/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ec2 마이그레이션]]></title>
            <link>https://velog.io/@sh_38/ec2-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@sh_38/ec2-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98</guid>
            <pubDate>Sat, 26 Apr 2025 13:42:31 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-상황">문제 상황</h2>
<hr>
<p>개발 환경과 실제 운영 환경을 분리하여 운영하기 위해 ec2 인스턴스를 추가로 생성하여
기존의 환경을 옮겨야 한다.</p>
<p>기존 컨테이너를 그대로 옮기는건 큰 문제가 되지 않는다.</p>
<ol>
<li><p>어떤 ec2 인스턴스가 우리 서비스에 가장 적합한가?</p>
</li>
<li><p>현재 운영중인 데이터베이스의 데이터들은 어떻게 옮길 것인가?</p>
</li>
</ol>
<br>

<h2 id="ec2-인스턴스-고르기">ec2 인스턴스 고르기</h2>
<hr>
<p>요금 문제로 ec2 프리티어를 자주 사용해서, 무료 인스턴스 생성은 이제 익숙하다.</p>
<p>하지만 실제 운영되는 서버에 사용될 인스턴스이기 때문에 신중하게 고를 필요가 있다.</p>
<p><a href="https://calculator.aws/#/createCalculator/ec2-enhancement">ec2 인스턴스 비용 계산 사이트</a></p>
<p><strong>체크해야 할 부분</strong></p>
<h3 id="1-ec2-서비스-지역-확인">1. ec2 서비스 지역 확인</h3>
<p>-&gt; 아시아 태평양(서울) 이랑 미국에 오하이오 처럼 <u>ec2 지역마다 대여 가격이 다르다.</u></p>
<br>

<h3 id="2-비용-결제-방식-확인">2. 비용 결제 방식 확인</h3>
<p>-&gt; 가장 중요한 <strong>&#39;비용&#39;에 대한 결제 방식</strong>을 고려한다. 3가지 방식을 소개하면</p>
<ul>
<li>온디맨드: 대여한 스펙에 맞게, 사용한 시간 만큼 결제</li>
<li>컴퓨팅 절감형: 약정형 대여 (할인 많이 해줌), 도중에 인스턴스 스펙 변경시 제약이 없음(당연히 추가비용은 발생 가능함)</li>
<li>EC2 인스턴스 절감형: 약정형 대여 (할인 많이 해줌), 도중 인스턴스 스펙 변경에 제약이 있을 수 있음(인스턴스 유형이 비슷한 것 끼리만 변경 가능)</li>
</ul>
<br>

<p><img src="https://velog.velcdn.com/images/sh_38/post/3cc884ec-d958-4dff-8fc5-b82c6bf314f5/image.png" alt=""></p>
<h3 id="3-인스턴스-유형">3. 인스턴스 유형</h3>
<p>-&gt; 대여할 인스턴스의 스펙을 고려한다.</p>
<p>현재 ec2 인스턴스를 사용중이고, 서비스하고 있는 중이면
<strong>htop</strong> 명령어를 통해 실시간 서버 리소스 사용량을 확인할 수 있다.</p>
<p>인스턴스 스펙 3요소를 보면</p>
<ul>
<li>CPU 성능
  -&gt; 현재 서비스의 cpu 사용량에 대해서 확인을 해본다. cpu 코어 개수가 많아질수록 가격 상승폭이 높음. <br>
  <strong>cpu 종류에 따라서</strong> 가격도 꽤 많이 차이난다.
  cpu 인스턴스들은 특정 문자들로 시작한다.
  cpu 시리즈 중에서 t 시리즈를 많이 고려했기 때문에 다른 시리즈는 좀 더 찾아보길 바란다.</li>
</ul>
<pre><code>1. t 시리즈
```
t 시리즈는 기본 성능은 낮다.
하지만 t 시리즈에는 &#39;버스트&#39; 라는 기능이 있다.

t 시리즈는 &#39;크레딧&#39; 이라는걸 cpu 사용량이 적을 때 계속 조금씩 쌓아둔다.
그러다가 cpu 사용량이 폭발적으로 늘어나는 순간이 오면 크레딧을 사용해서 성능 향상을 시킬수 있다.
t 시리즈 마다 cpu 사용량 기준이 되는 지점이 있는데 해당 지점을 넘게되면 &#39;버스트&#39;가 활성화 된다.

t1 : 초창기 버전이다. 현재 지원 x

t2 : cpu 사용량이 많아져서 크레딧을 전부 소비하게 되면 속도가 제한된다.
    -&gt; cpu 사용량 10~20% 이상시 버스트, 크레딧 소진시 속도 제한 걸림

t3 : unlimited 옵션을 켜면 추가 요금을 발생시키지만 속도를 유지할 수 있다.
    -&gt; cpu 사용량 20~30% 이상시 버스트, 크레딧 전부 소진해도 unlimited 옵션을 켜면 속도 유지 but 추가요금 발생

t4g : unlimited 옵션을 켜면 추가 요금을 발생시키지만 속도를 유지할 수 있다.
      &#39;g&#39; 가 붙었는데 g 는 아마존 자체 개발 인스턴스이고 그렇기에 &#39;더 저렴하고 더 빠르다&#39;라는 특징이 있음
      g가 붙은것들은 !!⭐ARM 기반의 인스턴스이다.⭐!!(ARM 기반의 프로세서와 내 운영 서비스가 호환하는지 잘 찾아보기)
    -&gt; cpu 사용량 20~30% 이상시 버스트, 크레딧 전부 소진해도 unlimited 옵션을 켜면 속도 유지 but 추가요금 발생

```
2. c 시리즈
```
c 시리즈는 cpu에 최적화된 인스턴스 유형이다.

cpu 처리량이 많은 서버의 경우 c 시리즈로 시작하는 인스턴스를 사용하는게 좋다.

```
3. r 시리즈
```
r 시리즈는 메모리에 최적화된 인스턴스 유형이다.

메모리 속도가 빠르고 많은 용량이 필요할 경우 선택하게 된다.

```
4. i 시리즈
```
i 시리즈는 스토리지에 최적화된 인스턴스 유형이다.

읽고 쓰기가 많은 경우 저장장치의 읽고 쓰는 속도가 중요한데 읽고 쓰기 작업의 속도가 중요할 때 사용하는걸 추천한다.

```</code></pre><br>

<ul>
<li><p>RAM 용량
  -&gt; 메모리 여유 공간을 확인한다. 평상시 메모리 사용량보다, 배포할 때 메모리 최대 사용량을 확인해보는게 좋다.
  서버가 켜져있을 때 보다 컨테이너들을 만들면서 메모리 부족으로 터지는 경우가 많았다.</p>
  <br>

<p>  -&gt; 인스턴스 뒤에 small, medium, large, xlarge 이렇게 붙은건 메모리 용량마다 차이가 난다.</p>
</li>
</ul>
<br>

<ul>
<li><p>저장 용량
  -&gt; 인스턴스에 저장되는 데이터가 많다면 크게 잡아줘야겠지만 엄청 클 필요는 없는것 같다.
  적당하게 30 ~ 50GB 으로 잡아서 사용중</p>
  <br>

<p>  -&gt;저장 용량이 많아질수록 당연히 가격이 상승하고, 또한 저장장치도 빠른걸 쓰면 그만큼 비용이 증가함</p>
  <br>

<p>  또한 &#39;스냅샷&#39; 이라는 기능이 있는데 특정 주기마다 백업을 하는 기능이다. <strong>이 기능을 사용하게 되면 추가 비용이 많이 발생하는것 같은데 그냥 자체로 백업을 하는게 맞다고 본다.</strong></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sh_38/post/2f339d96-1cc3-4e5c-bbc7-dc0c6d3a1d1d/image.png" alt=""></p>
<h2 id="데이터베이스-마이그레이션">데이터베이스 마이그레이션</h2>
<hr>
<p>인스턴스를 골라서 생성하고 나서
compose.yml을 옮기거나 각종 설정들을 다 해준 후 
실제 운영데이터들을 옮겨야 할 것이다.</p>
<h3 id="mysql">MySQL</h3>
<p>MySQL은 Data Exports를 해주면 된다.</p>
<p>보통 MySQL 쓸 때 cli 보다는 workbench를 많이 사용하는데</p>
<p>여기서 스키마 선택하고 Data Exports 해주면 dump 파일들이 생긴다.</p>
<p>이를 새로운 인스턴스에 접속해서 Data Import 해주면 끝</p>
<br>

<h3 id="mongodb">MongoDB</h3>
<p>MySQL에서 <strong>스키마(DB)</strong> 라고 하는거를 MongoDB 에서는 그냥 <strong>DB</strong> 라고 하고
MySQL에서의 <strong>테이블</strong>을 MongoDB에는 <strong>Collection</strong> 라고한다.
또한 mysql의 <strong>튜플</strong>은 mongoDB에서 <strong>document</strong> 라고 한다.</p>
<p>그런데 중요한 점은
MySQL workbench에서는 DB exports 가 되어서 스키마 내부에 테이블들을 <strong>한번에 백업할 수 있다.</strong></p>
<p>그런데 MongoDB 전용 툴인 MongoCompass 에서는 <strong>그게 안된다.</strong> 그래서 collection이 많다면 하나씩 옮기느라 시간이 오래걸릴 수 있다.</p>
<p>또한 각 DB 마다 <strong>&#39;계정&#39; 이 존재</strong>해서 이러한 정보들은 또 수동으로 옮겨줘야 한다.</p>
<p>나는 운영 mongo 안에 컬렉션이 너무 많아서 다른 방법을 찾았는데</p>
<p><u>컨테이너에 직접 접속해서 dump를 만들고 이를 밖으로 꺼내서 사용했다.</u></p>
<h3 id="mongodb-전체-db-dump-생성-후-복원">MongoDB 전체 DB dump 생성 후 복원</h3>
<ul>
<li>이전 서버에서 dump 파일 생성하기 (mongo컨테이너 내부에 저장됨)</li>
</ul>
<pre><code class="language-sh">docker exec -it mongo mongodump -u &lt;계정ID&gt; -p &lt;계정PW&gt; --authenticationDatabase admin --out /data/dump</code></pre>
<ul>
<li>컨테이너 내부 dump 파일을 컨테이너 밖에 복사 (docker cp 사용)</li>
</ul>
<pre><code class="language-sh">docker cp mongo:/data/dump ./dump</code></pre>
<ul>
<li>FTP로 해당 dump 파일을 새로운 서버로 옮긴 후 dump파일을 mongoDB 컨테이너 안으로 복사</li>
</ul>
<pre><code class="language-sh">docker cp /home/ubuntu/dump mongo:/data/dump</code></pre>
<ul>
<li>mongo 컨테이너 접속해서 mongorestore 하기</li>
</ul>
<pre><code class="language-sh">docker exec -it mongo mongorestore -u &lt;계정ID&gt; -p &lt;계정PW&gt; --authenticationDatabase admin /data/dump</code></pre>
<p>이러한 과정을 거치게 되면 내부 데이터를 복제해서 옮기는 것이기 때문에
온전한 데이터 이전이 가능하다.</p>
<p>물론 옮기는 과정 중 발생하는 데이터는 유실되므로 서비스를 중지하고 옮겨야 한다.</p>
<hr>
<p>이렇게 ec2 이전 작업을 해보았다.</p>
<p>끝</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@Notfound]]></title>
            <link>https://velog.io/@sh_38/Notfound</link>
            <guid>https://velog.io/@sh_38/Notfound</guid>
            <pubDate>Mon, 21 Apr 2025 06:40:21 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-상황">문제 상황</h3>
<blockquote>
<p>개발을 하다가 엔티티 내부에 연관관계로 매핑된 다른 엔티티를 불러올 때 column 값은 있지만 해당 데이터가 DB에서 없는 데이터일 경우
쿼리 단에서 예외가 발생한다. 이를 어떻게 해결해야 할까?</p>
</blockquote>
<h3 id="1-board-에서-user의-엔티티를-들고있을-때">1. Board 에서 User의 엔티티를 들고있을 때</h3>
<hr>
<pre><code class="language-java">//Board 엔티티 내부 필드
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = &quot;user_id&quot;)
    private User user;
</code></pre>
<p>Board 라는 엔티티 내부의 user 필드이다.</p>
<p>만약 user_id 가 <code>null</code> 인 값이 들어오면 어떻게 되나?
(board 테이블에 한 튜플의 user_id 컬럼이 null 인 경우)</p>
<p>-&gt; 이런 경우 FetchType.LAZY, EAGER에 관계없이 찾으려는 시도조차 안한다.</p>
<p><strong>즉 user_id에 null 값이 들어있으면 그냥 Board만 딱 들고 온다는 것</strong></p>
<p>그러면 user에는 null이 들어가있는다.</p>
<p>그리고 이는 optional 이라는 옵션이 true로 기본적으로 설정되어 있기 때문 (디폴트가 true라 안보임)</p>
<pre><code class="language-java">//Board 엔티티 내부 필드
    @ManyToOne(fetch = FetchType.EAGER, optional = true)
    @JoinColumn(name = &quot;user_id&quot;)
    private User user;
</code></pre>
<p><strong>핵심은 시도조차 안한다는 것 -&gt; 쿼리가 안날아가니 문제될 게 없음.</strong></p>
<h3 id="위-경우에서-user에-접근하는-로직이-있다면">위 경우에서 user에 접근하는 로직이 있다면?</h3>
<p>당연히 NPE가 발생할 것이다.</p>
<p>그럼 필드를 <code>Optional&lt;User&gt;</code>로 만들어 주면 되겠네? -&gt; X</p>
<p>Optional은 엔티티 필드에 쓰면 안된다고 한다. (<u>JPA 스펙상 그렇다고 함, JPA에서 사용 금지함</u>)</p>
<p>그럼 어떻게 하지...</p>
<br>

<p>서비스단에서 Optional로 가야 한다.</p>
<pre><code class="language-java">Optional&lt;User&gt; optionalUser = Optional.ofNullable(board.getUser());
</code></pre>
<p>이렇게 통하면 null-safe 하게 사용할 수 있다.</p>
<p>-&gt; 하지만 나는 null 체크를 잘 해야한다는 설명을 하고있는게 아니다.</p>
<br>

<h3 id="2-board-내부-user-id가-존재할-때">2. Board 내부 User id가 &quot;존재할 때&quot;</h3>
<hr>
<p>설명하고 싶은 상황은 이러한 상황이다.</p>
<blockquote>
<ol>
<li>Board 내부에는 User가 매핑되어 있다.</li>
<li>user_id pk 값으로 DB에서 찾게된다.</li>
<li>Board 테이블 내에는 user_id로 &#39;10&#39; 이라는 값이 존재한다.</li>
<li>User 테이블에서 pk 10을 가진 user는 없는 경우</li>
</ol>
</blockquote>
<p>일반적으로 구현할 때 유저가 회원 탈퇴를 하면 <strong>soft_delete</strong>를 한다.</p>
<p>&quot;is_delete&quot; 라는 컬럼을 User에 두고 삭제가 된다면 true 로 바꿔주는 그런식의 삭제 로직을 사용하는데</p>
<p>이러한 세부적인 정책은 결국 <strong>고객의 요구사항에 맞게 반영해야 하는 부분</strong>이기 때문에 달라질 수 있다.</p>
<p>그래서 해당 프로젝트에서는 회원 탈퇴 시 <strong>hard-delete</strong> 즉 데이터를 삭제하는 방향으로 구현하게 되었다.</p>
<p>Board 엔티티를 가져오면 fetchType에 따라 User를 바로 가져올 수도 있고, 아니면 필요할때 가져올 수 있다.</p>
<h3 id="결국-user-엔티티를-조회하게-되는데-이-때-user_id-기반으로-찾을-수-없는-데이터라면">결국 User 엔티티를 조회하게 되는데 이 때 &quot;user_id&quot; 기반으로 찾을 수 없는 데이터라면???</h3>
<p>-&gt; SQL 에러가 발생한다.</p>
<p>이를 어떻게 해결했나?</p>
<br>


<h3 id="notfoundaction--notfoundactionignore">@notfound(action = notfoundaction.ignore)</h3>
<hr>
<p>이는 JPA의 기능은 아니고 hibernate에서 지원하는 기능이다.</p>
<blockquote>
<p><a href="https://docs.jboss.org/hibernate/orm/6.6/userguide/html_single/Hibernate_User_Guide.html#associations-not-found">hibernate 공식문서 링크</a></p>
</blockquote>
<pre><code class="language-java">//Board 엔티티 내부 필드
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = &quot;user_id&quot;)
    @notfound(action = notfoundaction.ignore)
    private User user;
</code></pre>
<p>만약 user_id로 DB에서 검색을 했더니 데이터가 <strong>없는 데이터다? 그러면 예외를 발생시키지 말고 null을 넣어라</strong></p>
<p>라는 어노테이션이다.</p>
<p>마치 Java Optional을 트랜잭션에 적용한 느낌이다.</p>
<p>나는 이런 어노테이션을 사용해서 예외 처리를 할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[페이지네이션]]></title>
            <link>https://velog.io/@sh_38/%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98-ngygoln7</link>
            <guid>https://velog.io/@sh_38/%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98-ngygoln7</guid>
            <pubDate>Tue, 15 Apr 2025 15:58:41 GMT</pubDate>
            <description><![CDATA[<p>다른 프로젝트를 하게 되면서 게시판 파트를 맡았고
게시글 리스트를 불러오게 되었다.</p>
<h1 id="1-pageable이란">1. Pageable이란</h1>
<hr>
<p>pageable은 Spring Data JPA 에서 목록을 불러올 때 쉽게 가져올 수 있도록 객체로 구현해 둔 클래스이다.</p>
<p>특정 게시판의 1) <u>몇 번째 페이지를</u> 2) <u>몇 개 만큼</u> 3) <u>어떤 기준으로</u> 불러올 것인지를 결정할 수 있다.
<br></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/ec5a6e60-27b6-4f9a-a56b-d72b789dad53/image.png" alt=""></p>
<p>또한 정렬 기준을 정할 수 있어서 다양한 게시판의 종류에 맞게 사용할 수 있다.
(좋아요 100개 이상의 글, 가장 최신 글 등... )</p>
<br>


<br>

<h1 id="2-사용법">2. 사용법</h1>
<hr>
<h3 id="1pagerequest">1.PageRequest</h3>
<pre><code class="language-java">Pageable pageable = PageRequest.of(페이지번호, 한페이지에몇개);

Pageable pageable = PageRequest.of(페이지번호, 한페이지에몇개, 정렬조건);</code></pre>
<p>Pageable을 사용하기 위해서는 PageRequest 객체를 통해 생성한다.</p>
<ul>
<li><p>페이지 번호는 0 부터 시작하고 0이 첫번째 페이지이다.</p>
</li>
<li><p>페이지 당 몇개 는 원하는 개수만큼 불러올 수 있다.</p>
</li>
<li><p>마지막으로 정렬 조건은 Sort라는 객체를 통해 추가할 수 있다.</p>
<br>

</li>
</ul>
<h3 id="2-sort">2. Sort</h3>
<p>Sort는 흔히 SQL의 <u>orderby</u>의 역할을 한다.</p>
<pre><code class="language-java">Sort sort = Sort.by(&quot;createdAt&quot;);</code></pre>
<p>이렇게 <code>Sort</code>를 만들었다면 정렬 기준은 페이지네이션 할 엔티티의 createdAt 필드의 오름차순으로 정렬하게 된다.</p>
<h4 id="내림차순으로-하려면">내림차순으로 하려면?</h4>
<pre><code class="language-java">Sort sort = Sort.by(Sort.Direction.DESC, &quot;createdAt&quot;);</code></pre>
<p>혹은 </p>
<pre><code class="language-java">Sort sort = Sort.by(&quot;createdAt&quot;).descending();

Sort sort = Sort.by(&quot;createdAt&quot;).descending().and(Sort.by(&quot;likes&quot;).descending());</code></pre>
<p>이렇게 체이닝(Chaining) 방식을 통해 표현할 수 있다.</p>
<p>나는 아래의 방법을 자주 사용할 것 같다. ( 읽기 편하기 때문 )</p>
<h3 id="repository-에서는">Repository 에서는?</h3>
<pre><code class="language-java">
public interface BoardRepository extends JpaRepository&lt;Board, Long&gt; {

    Page&lt;Board&gt; findAll(Pageable pageable);
}</code></pre>
<p>메서드에 <code>Pageable</code>을 넣어주고 원하는 객체 타입의 <code>Page</code>를 가져가면 된다!</p>
<p>-&gt; 만약 추가적인 쿼리가 필요하다면?</p>
<pre><code class="language-java">
    @Query(&quot;SELECT b FROM BOARD b WHERE b.status = :status&quot;)
    Page&lt;Board&gt; findAllByStatus(Pageable pageable, @Param(&quot;status&quot;) String status);
</code></pre>
<p>JPQL에 파라미터로 추가해주면 된다</p>
<p>이렇게 만들어진 <code>Page&lt;T&gt;</code> 객체는 어떻게 쓸 수 있을까?</p>
<p>일반적으로는 <code>.getContent()</code>를 통해서 &#39;현재 페이지 목록&#39; List를 가져올 수 있다.</p>
<p><strong>해당 List는 무엇으로 이루어져있나?</strong></p>
<p><code>Page</code>에 받은 Entity 객체 타입이다. 즉 위 예시에서는 <code>Board</code>를 받아온다.</p>
<br>


<br>


<h1 id="3-활용">3. 활용</h1>
<hr>
<h3 id="1-page-객체의-정보">1. page 객체의 정보</h3>
<p>page 객체에서 가져올 수 있는 정보는 다음과 같다.</p>
<pre><code class="language-java">page.getTotalElements();  =&gt; 전체 항목 수
page.getTotalPages();  =&gt; 전체 페이지 수
page.getNumber();  =&gt; 현재 페이지 번호
page.getSize();  =&gt; 한 페이지당 개수
</code></pre>
<p>이런 정보들을 넘겨주면 프론트에서 받아서 페이지네이션에 사용할 수 있다.</p>
<h3 id="2-무한스크롤">2. 무한스크롤</h3>
<p>무한 스크롤은 프론트에서 요청할 때 다음 페이지에 대한 정보를 주면서 요청하는 것이다.</p>
<pre><code class="language-java">
@Query(&quot;&quot;&quot;
    SELECT b FROM Board b
    WHERE (:lastLikes IS NULL OR 
          (b.likes &lt; :lastLikes OR (b.likes = :lastLikes AND b.id &lt; :lastId)))
    ORDER BY b.likes DESC, b.id DESC
&quot;&quot;&quot;)
List&lt;Board&gt; findNextPageByLikes(
    @Param(&quot;lastLikes&quot;) Integer lastLikes,
    @Param(&quot;lastId&quot;) Long lastId,
    Pageable pageable
);
</code></pre>
<p>좋아요 기준으로 무한스크롤을 한다고 하면 위와 같이 구현하게 된다.</p>
<p>중요한것은 프론트에서 다음 좋아요 개수(lastLikes)로 다음 페이지를 요청할 때,
분명 <strong>좋아요 개수가 겹치는 글들이 많을것</strong>이다.</p>
<p>이를 제대로 처리해주지 않으면 중복되는 글들이 섞여서 나오겠지?</p>
<p>그렇기 때문에 게시글 id를 받아와서 사용한다.</p>
<h3 id="3-enum-클래스">3. enum 클래스</h3>
<p>미리 enum 클래스에 선언해놓고 가져다 쓰는 방식으로 사용할 수 있다.</p>
<p>이렇게 쓰면 매번 Sort를 만드는데 필드명을 직접 적을 필요가 없다. (오타 방지)</p>
<p>자주 쓰는 정렬 방식은 enum 클래스로 정의해두고 사용하면 좋다.</p>
<pre><code class="language-java">
@Getter
@RequiredArgsConstructor
public enum BoardSort {

    // 인기순: 좋아요 내림차순, 동일한 좋아요 수면 최신순
    POPULAR(Sort.by(&quot;likes&quot;).descending().and(Sort.by(&quot;createdAt&quot;).descending())),

    // 최신순
    RECENT(Sort.by(&quot;createdAt&quot;).descending());

    private final Sort sort;
}
</code></pre>
<br>

<p>아래에서는 컨트롤러 부분인데 여기서 BoardSort가 enum 클래스이기 때문에 <strong><u>자동으로 valueOf() 가 동작해서 매핑</u></strong>한다.</p>
<p>orderby=POPULAR로 요청이 들어오면 orderby=BoardSort.valueOf(&quot;RECENT&quot;) 가 동작한다는 뜻</p>
<br>

<pre><code class="language-java">@GetMapping(&quot;/boards&quot;)
public ResponseEntity&lt;List&lt;BoardMetadata&gt;&gt; getBoardList(
    @RequestParam int pgno,
    @RequestParam int count,
    @RequestParam(defaultValue = &quot;RECENT&quot;) BoardSort orderby
) {
    List&lt;BoardMetadata&gt; boards = boardService.getBoards(pgno, count, orderby.getSort());
    return ResponseEntity.ok(boards);
}</code></pre>
<br>

<p>-끝-</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 시작]]></title>
            <link>https://velog.io/@sh_38/Spring-Security-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@sh_38/Spring-Security-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Thu, 20 Mar 2025 03:39:44 GMT</pubDate>
            <description><![CDATA[<h3 id="시큐리티-동작-과정">시큐리티 동작 과정</h3>
<p>HTTP 요청이 서버로 들어가면</p>
<p>Servlet Container 안에 Dispatcher Servlet이 받게 되는데</p>
<p>이 전에 들어오는 요청들은 필터에 걸러진다.</p>
<p>Spring Security는 이 필터에서 들어오는 요청이</p>
<p>안전한 요청인지, 인가 있는 요청인지 확인한다.</p>
<hr>
<p>build.gradle 에 시큐리티 의존성을 등록을 해두면</p>
<p>이 프로젝트에 뭐 따로 해주지 않아도 알아서 전체 프로젝트에 적용이 된다.</p>
<p>프로젝트를 시작하면 아래처럼 비밀번호 알려주고 로그인을 하게 만듦</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/6108c2ff-a6fc-4cf0-bb52-f0ad8cf762a6/image.png" alt=""></p>
<p>초기 ID 는 user,  PW 는 사진에 나오는 랜덤으로 생성된 문자열이다.</p>
<h3 id="그러면-로그인은-어떻게-할-수-있나">그러면 로그인은 어떻게 할 수 있나?</h3>
<ol>
<li><p>브라우저</p>
<p> 브라우저에서는 해당 서버의 주소로 들어가는데 경로는 <code>/login</code> 이다.</p>
<p> 로컬호스트에서 돌리는 경우는</p>
<p> <code>localhost:8080/api/login</code> 이 되겠다. (context-path가 /api로 설정되어 있음)</p>
<p> 나는 컨트롤러에서 login을 매핑해주지 않았지만 시큐리티가 처음 설정되면 알아서 login 경로로 로그인할 수 있도록 만들어준다.</p>
<ul>
<li>접속 시 화면</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_38/post/072f9b70-7b98-4130-849d-1f02a6355a0a/image.png" alt=""></p>
<ol>
<li><p>Postman</p>
<p> Postman으로도 로그인할 수 있다.</p>
<ul>
<li>요청 시 설정</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_38/post/f5e5033f-ff02-4dc4-87fb-d498a4a1a27b/image.png" alt=""></p>
<pre><code>나는 현재 서버에 테스트용으로 `/auth/main` 을 매핑해둔 상태이다. 여기에 요청을 보내기 전에 Authorization 탭에서 ‘Basic Auth’로 선택후 Username, Password 입력해둔다. 

여기서 왜 admin에 admin 이냐면 내가 application.yml 에 따로 설정해두었기 때문

```yaml
security:
  user:
    name: admin
    password: admin
```</code></pre><hr>
<h3 id="security-config">Security Config</h3>
<p>이 클래스를 만들어서 내가 원하는 곳에만 인증이 필요하도록 설정할 수 있다.</p>
<p>얘가 없으면 기본적으로 모든 요청은 인가작업이 필요하도록 설정되어있다.</p>
<p>우선 config 디렉토리에</p>
<p>SecurityConfig 클래스 만들어주고</p>
<p><code>@Configuration</code> 과 <code>@EnableWebSecurity</code> 를 붙여준다.</p>
<ol>
<li><strong>@Configuration</strong> 은 </li>
</ol>
<blockquote>
<p>해당 클래스는 스프링 앱에서 사용하는 설정파일입니다.</p>
</blockquote>
<p>라고 선언해주는 기능이다.</p>
<ol>
<li><strong>@EnableWebSecurity</strong> 는</li>
</ol>
<blockquote>
<p>해당 클래스가 Security 설정을 해주는 클래스입니다.</p>
</blockquote>
<p>라고 선언해주는것이다. 즉 Spring Security 설정파일로 만드는 어노테이션임</p>
<p>그런데 <code>@EnableWebSecurity</code> 안에는 <code>@Configuration</code> 이 포함되어 있다. (파고파고 들어가면 나옴)</p>
<p>근데 왜 또 붙이는거지?</p>
<p>→ 해당 클래스가 설정파일임을 알리기 위한 ‘관례’ 라고 한다.</p>
<p>그러니까 <code>@Configuration</code> 어노테이션이 붙어있으면 ‘일단 얘는 설정파일이다.’ 라고 개발자들에게 알려주는거다.</p>
<hr>
<p>우리는</p>
<blockquote>
<p>어떤 페이지는 로그인 안해도, 어떤페이지는 로그인 해야 볼 수 있게</p>
</blockquote>
<p> <strong>‘커스텀 거름망’</strong>을 만드려면 SecurityFilterChain 이라는 것을 <strong>빈</strong>으로 등록해둬야 한다.</p>
<br>

<p>Spring Security는 <strong>‘보안 필터 체인’</strong> 이라는 것을 만들어야 한다.</p>
<pre><code>*보안 필터 체인(Security Filter Chain)이란?

HTTP 요청이 들어오면 필터에 걸러져서 들어온다고 했다.

그 필터를 사슬처럼 엮어둔 것을 필터체인이라고 하고

이걸 Spring Security가 만들어준다.</code></pre><p>근데 Security Config는 <code>@Configuration</code>이기 때문에 앱 시작 전 설정을 해 주는 클래스다.</p>
<p>그러면서 Security에 대한 설정을 만드는데 필요한 것이 <code>SecurityFilterChain</code></p>
<p>그러기 위해서는 필터체인을 설정하는 해당 객체가 빈으로 등록되어 있어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/5d7f439a-5b4f-4f92-8b52-7db7f8cedeb0/image.png" alt=""></p>
<hr>
<p>일단 간단하게 아래와 같이 SecurityFilterChian을 만들어봤다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/e9191645-8e4b-42fc-934c-7d213951084f/image.png" alt=""></p>
<p>우리는 ‘커스텀 거름망’을 사용할 것이기 때문에</p>
<p>HttpSecurity 를 받아서 설정해주고 다시 리턴해주면 된다.</p>
<p>(return에 보면 build로 되어있다. SecurityFilterChain은 Builder 패턴을 사용하고 있음.)</p>
<br>

<p>대충 보면 <code>requestMatchers</code>랑 뒤에 뭐 붙어있는 형식이다.</p>
<ol>
<li><p><code>requestMatchers</code> 에는 적용할 context를 설정해줄 수 있다.</p>
</li>
<li><p>그리고 그 뒤에는 접근 가능한 대상을 설정해주고 있다. (permitAll = 누구에게나 개방)</p>
</li>
<li><p>.anyRequest는? if else 문에서 else와 같다.</p>
</li>
</ol>
<p>상당히 간단한 구조로 보인다.</p>
<ul>
<li>이렇게 설정하면 /auth/main에 들어갈 수 있을까?  → ( X )</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sh_38/post/3cfc7719-3d1d-4cad-a98d-0a9c424ae0dd/image.png" alt=""></p>
<p>안된다. /auth 안에 모든 경로에 접근가능하게 하려면 상대경로의 느낌으로 /* 을 붙여야 한다.</p>
<p>기본적인 Spring SecurityFilterChain 을 한번 만들어 봤다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[muzip] SSL 자동갱신]]></title>
            <link>https://velog.io/@sh_38/muzip-SSL-%EC%9E%90%EB%8F%99%EA%B0%B1%EC%8B%A0</link>
            <guid>https://velog.io/@sh_38/muzip-SSL-%EC%9E%90%EB%8F%99%EA%B0%B1%EC%8B%A0</guid>
            <pubDate>Tue, 11 Mar 2025 14:17:38 GMT</pubDate>
            <description><![CDATA[<p>certbot을 사용하여 webroot 방식을 통해 SSL 자동 갱신 설정하기.</p>
<br>

<p>시작하기에 앞서</p>
<ol>
<li>나는 현재 docker-compose로 프론트 백 같이 빌드하고 있고,</li>
<li>nginx에서 사용할 default.conf 파일은 마운트 된 상태이다.</li>
<li>nginx는 컨테이너로 관리하고 있다.</li>
</ol>
<br>

<p>아래의 4개를 신경써서 해야한다.</p>
<ul>
<li>도커 볼륨 마운트</li>
<li>nginx의 .conf 파일</li>
<li>certbot 명령어</li>
<li>crontab 설정</li>
</ul>
<p>나는 이거 설정해주는데 몇 시간을 날렸는데
경로 설정, 명령어 경로 설정, 디렉토리 이름 설정 이런 부분에서 한 걸음 나갈때마다 막혔다...</p>
<h2 id="webroot">webroot?</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/sh_38/post/56625460-a442-4706-b420-99ef27d1f2a5/image.png" alt=""></p>
<p>요약하자면, certbot을 계속 돌려둘 필요 없이 + 서비스의 중단 없이 ssl 인증을 자동으로 갱신할 수 있는 방법이다.</p>
<h2 id="1-certbot-설치">1. certbot 설치</h2>
<pre><code class="language-shell">sudo apt update
sudo apt install certbot</code></pre>
<p>설치하고 확인하기</p>
<pre><code class="language-shell">certbot --version</code></pre>
<h2 id="2-인증서-받기-위한-설정">2. 인증서 받기 위한 설정</h2>
<p>Let&#39;sEncrypt가 도메인 소유권을 확인하기 위해서</p>
<pre><code>http://muzip.store/.well-known/acme-challenge/</code></pre><p>이 주소에 인증 파일이 있나 없나 확인한다.</p>
<p>webroot 방식은 해당 주소에 인증 파일을 올려서 검증받는 방식이다.</p>
<p>그렇기에 Let&#39;sEncrypt에서 접속할 수 있도록 default.conf 설정을 아래와 같이 해준다.</p>
<pre><code>server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
        allow all;
    }
}</code></pre><p>여기서 <u><strong>경로 이름이 중요한지 모르겠지만</strong></u> gpt는 <code>/var/www/html</code> 으로 하라고 했다가 <code>/var/www/cerbot</code>으로 하라고 했다가 그러길래 둘다 해봤는데 <code>certbot</code>으로 했을 때 동작했다.</p>
<p>경로가 영향을 미치는건지는 잘 모르겠음..</p>
<h4 id="그다음">그다음</h4>
<p><img src="https://velog.velcdn.com/images/sh_38/post/65d052bf-995b-4189-bbb5-ecab9e60e54f/image.png" alt=""></p>
<p>docker-compose.yml 에서 프론트 컨테이너(nginx)의 볼륨 마운트 부분이다.</p>
<p>위에서 사용한 경로와 리눅스 호스트에서 인증서 확인에 필요한 디렉토리를 연결을 해 줘야한다.</p>
<p>또한 여기서 <code>/home/ubuntu/certbot-webroot</code> 디렉토리 안에 <code>.well-known/acme-challenge/</code> 이 디렉토리 까지 같이 만들어 놓는다</p>
<p>그 후에 <code>chmod -R 755 /path/to/certbot-webroot</code>로 권한 부여 까지 해주기</p>
<br>

<h2 id="3-certbot으로-인증서-발급">3. Certbot으로 인증서 발급</h2>
<pre><code>sudo certbot certonly --webroot -w /home/ubuntu/certbot-webroot -d muzip.store</code></pre><p>여기서는 명령어를 잘 확인해야 한다.</p>
<p>-w 뒤에는 자신이 만들어둔 디렉토리로 설정해주고 -d 뒤에는 자신의 도메인을 넣어준다.</p>
<p>해당 도메인의 소유권을 검증받기 위해 우리의 도메인 주소를 넣는다.</p>
<p>명령어를 넣으면 인증서가 발급될 것이다.</p>
<p>여기서!! 저 디렉토리 안에 뭐가 생기는건 <u><strong>아니다!</strong></u></p>
<p>명령어를 실행하면 certbot이 해당 디렉토리 안에 인증을 위한 파일을 넣고 Let&#39;sEncrypt가 그 파일을 확인하기 위해 우리 서버에 접속 후 확인이 되면 인증서를 발급하면서 저 certbot-webroot 디렉토리 안에 파일을 삭제한다.</p>
<p>그래서 우리 눈에는 아무것도 안보임</p>
<h2 id="4-crontab-으로-자동화">4. crontab 으로 자동화</h2>
<p>crontab을 통해 알아서 갱신되도록 하기 전에</p>
<p><code>sudo certbot renew --dry-run</code> 을 통해
인증서가 갱신되는지 확인한다.</p>
<p><strong>인증서가 갱신이 되는걸 확인했으면</strong></p>
<p><code>sudo crontab -e</code> 을 통해 스케쥴러 편집창을 열고</p>
<p><code>0 3 * * * certbot renew --quiet &amp;&amp; docker exec muzipfront nginx -s reload</code> 
새벽 3시에 인증서를 갱신하고 muzipfront 컨테이너에서 nginx 설정을 다시 reload 하라는 명령을 넣어준다.</p>
<hr>
<p>일단 이렇게 설정을 해뒀으니 되는지 안되는지는 3달 후에 알 수 있지 않을까? ㅋㅋ;;</p>
<p>현재 인증서는 갱신했기 때문에 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[완탐] 종이 조각 - java]]></title>
            <link>https://velog.io/@sh_38/%EC%99%84%ED%83%90-%EC%A2%85%EC%9D%B4-%EC%A1%B0%EA%B0%81-java</link>
            <guid>https://velog.io/@sh_38/%EC%99%84%ED%83%90-%EC%A2%85%EC%9D%B4-%EC%A1%B0%EA%B0%81-java</guid>
            <pubDate>Wed, 05 Mar 2025 03:36:26 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/14391">https://www.acmicpc.net/problem/14391</a></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/700f00c3-9db2-47f5-92fd-b6c95123ae50/image.png" alt=""></p>
<p>종이를 여러 조각으로 잘라서 합을 구하는 문제</p>
<hr>
<h3 id="풀이-방법">&lt;풀이 방법&gt;</h3>
<p><strong>1차 풀이 방법</strong> - 시간 초과</p>
<p>dfs</p>
<p>각 칸에서 가로길이, 세로길이 가능한 만큼 확인하고 dfs로 다음 칸으로 넘어간다.</p>
<ul>
<li>시간초과 이유 =&gt;</li>
</ul>
<p>n,m 이 4,4 일 때 한 칸에서 고려하는 경우의 수 = (가로 0,1,2,3) + (세로 0,1,2,3) = 8</p>
<p>탐색할수록 경우의 수는 줄어들긴 하지만 경우가 제곱으로 커짐</p>
<p>또한 계산하지 않아도 되는 경우까지 계산하게 됨.</p>
<p>-&gt; 한 줄에 가로2칸 + 가로2칸 이면 따로 계산하는게 아니라
가로4칸으로 계산해서 하나로 이은 값이 더 크다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/33b4de07-24a0-4167-9a5f-71320b7d5d99/image.png" alt=""></p>
<p><strong>2차 풀이 방법</strong> - 정답</p>
<p>마찬가지로 dfs 이지만</p>
<p>각 칸을 가로칸으로 배치할 것인지 세로칸으로 배치할 것인지 정한다.</p>
<p>모든 칸을 전부 정했으면 <strong>붙어있는 칸이 같은칸이면 자릿수를 올려가며 더해주기</strong>.</p>
<pre><code class="language-java"></code></pre>
<hr>
<h3 id="시간-초과">&lt;시간 초과&gt;</h3>
<pre><code class="language-java">import java.util.*;
import java.io.*;

public class Main {

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StringBuilder sb;
    static StringTokenizer st;

    static int n, m, count, max = Integer.MIN_VALUE;
    static int[][] arr;
    static int[][] visited;

    // 오른쪽, 아래쪽만 고려
    static int[] dx = {1, 0};
    static int[] dy = {0, 1};

    public static void main(String[] args) throws IOException {
        st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        arr = new int[n][m];
        visited = new int[n][m];

        for (int i = 0 ; i &lt; n ; i ++) {
            String str = br.readLine();
            for (int j = 0 ; j &lt; m ; j ++) {
                arr[i][j] = str.charAt(j) - &#39;0&#39;;
                visited[i][j] = -1;
            }
        }

        dfs(0, 0, 0, 0);

        System.out.println(max);
    }

    static void dfs(int depth, int sum, int x, int y) {
        // 모든 칸을 전부 확인 했으면 합 계산
        if (depth == n * m) {
            if (sum &gt; max) max = sum;
            return;
        }

        for (int i = x ; i &lt; n ; i ++) {
            for (int j = (i == x ? y : 0) ; j &lt; m ; j ++) {
                if (visited[i][j] == -1) {
                    // 세로로 얼마나 갈 지 정해서 가기
                    for (int h = 0 ; h &lt; n - i ; h ++) {
                        // 이미 다른 종이 조각이 있으면 안됨.
                        if (avail(i, j, h, 0)) {
                            int val = chkLine(i, j, h, 0, count++);
                            dfs(depth + h + 1, sum + val, i, j);
                            chkLine(i, j, h, 0, -1);
                            count --;
                        }
                    }
                    // 가로로 얼마나 갈 지
                    for (int w = 0 ; w &lt; m - j ; w ++) {
                        if (avail(i, j, w, 1)) {
                            int val = chkLine(i, j, w, 1, count++);
                            dfs(depth + w + 1, sum + val, i, j);
                            chkLine(i, j, w, 1, -1);
                            count --;
                        }
                    }
                }
            }
        }

    }

    // 갈 수 있는곳인지 검증하고 들어와야 함.
    // dir: 0 =&gt; 아래쪽, dir: 1 =&gt; 오른쪽
    static int chkLine(int x, int y, int len, int dir, int count) {
        int sum = 0;
        int temp = (int)Math.pow(10, len);

        for (int i = 0 ; i &lt; len + 1 ; i ++) {
            int nx = x + dx[dir] * i;
            int ny = y + dy[dir] * i;
            visited[nx][ny] = count;

            sum += arr[nx][ny] * temp;
            temp /= 10;
        }

        return sum;
    }

    static boolean avail(int x, int y, int len, int dir) {
        for (int i = 0 ; i &lt; len + 1 ; i ++) {
            int nx = x + dx[dir] * i;
            int ny = y + dy[dir] * i;

            if (visited[nx][ny] != -1) return false;
        }

        return true;
    }

}

</code></pre>
<hr>
<h3 id="정답-코드">&lt;정답 코드&gt;</h3>
<pre><code class="language-java">import java.util.*;
import java.io.*;

public class Main {

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StringTokenizer st;

    static int n, m, max = 0;
    static int[][] arr;
    static boolean[][] visited;

    public static void main(String[] args) throws IOException {
        st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        arr = new int[n][m];
        visited = new boolean[n][m];

        for (int i = 0; i &lt; n; i++) {
            String str = br.readLine();
            for (int j = 0; j &lt; m; j++) {
                arr[i][j] = str.charAt(j) - &#39;0&#39;;
            }
        }

        dfs(0, 0);

        System.out.println(max);
    }

    // 각 칸에 대해 가로/세로 선택
    static void dfs(int x, int y) {
        if (x == n) {
            calc();
            return;
        }

        int nextX = x;
        int nextY = y + 1;
        if (nextY == m) {
            nextX++;
            nextY = 0;
        }

        // 가로 조각으로 선택
        visited[x][y] = true;
        dfs(nextX, nextY);

        // 세로 조각으로 선택
        visited[x][y] = false;
        dfs(nextX, nextY);
    }

    // 종이 조각 합계 계산
    static void calc() {
        int sum = 0;

        // 가로 조각 계산
        for (int i = 0; i &lt; n; i++) {
            int temp = 0;
            for (int j = 0; j &lt; m; j++) {
                if (visited[i][j]) { // 가로 조각
                    temp = temp * 10 + arr[i][j];
                } else {
                    sum += temp;
                    temp = 0;
                }
            }
            sum += temp; // 줄 끝나면 더하기
        }

        // 세로 조각 계산
        for (int j = 0; j &lt; m; j++) {
            int temp = 0;
            for (int i = 0; i &lt; n; i++) {
                if (!visited[i][j]) { // 세로 조각
                    temp = temp * 10 + arr[i][j];
                } else {
                    sum += temp;
                    temp = 0;
                }
            }
            sum += temp; // 줄 끝나면 더하기
        }

        max = Math.max(max, sum);
    }
}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[완탐] 호석이 두 마리 치킨 - java]]></title>
            <link>https://velog.io/@sh_38/%ED%98%B8%EC%84%9D%EC%9D%B4-%EB%91%90-%EB%A7%88%EB%A6%AC-%EC%B9%98%ED%82%A8-Java</link>
            <guid>https://velog.io/@sh_38/%ED%98%B8%EC%84%9D%EC%9D%B4-%EB%91%90-%EB%A7%88%EB%A6%AC-%EC%B9%98%ED%82%A8-Java</guid>
            <pubDate>Tue, 04 Mar 2025 13:49:44 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/21278">https://www.acmicpc.net/problem/21278</a></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/ee96d8b4-1404-4242-bd80-98aa21882f57/image.png" alt=""></p>
<hr>
<h3 id="풀이-방법">&lt;풀이 방법&gt;</h3>
<ol>
<li><p>각 <strong>건물에서 건물 까지의 거리</strong>를 구해놓고</p>
</li>
<li><p>어떤 건물에 치킨집을 차릴 지 <strong>조합</strong>을 구한다.</p>
</li>
<li><p>건물에서 치킨집 사이의 거리를 구하는데 <strong>더 가까운 곳</strong>으로 계산한다.</p>
</li>
</ol>
<hr>
<blockquote>
<p>각 건물 사이의 거리 구하기</p>
</blockquote>
<p>여기서는 <strong>&#39;플로이드-워셜&#39;</strong> 을 사용하였다.</p>
<p>n 은 0 ~ 100인 만큼 100^3 해도 100만으로 제한시간 안에 계산이 가능하다.</p>
<p>&#39;플로이드-워셜&#39; 에서의 중요한 점은</p>
<p>3중 for문의 가장 바깥 for문이 <strong>&#39;거쳐가는 노드&#39;</strong> 인덱스여야 한다는 것.</p>
<p>그렇지 않으면 최신화된 정보를 통해 갱신하는것이 아니라 뒤죽박죽이 됨.</p>
<hr>
<h3 id="정답-코드">&lt;정답 코드&gt;</h3>
<pre><code class="language-java">import java.util.*;
import java.io.*;

public class Main {

    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StringBuilder sb;
    static StringTokenizer st;

    static int n, m, min = Integer.MAX_VALUE, resultX, resultY;
    static int[] chicken;
    static int[][] arr;
    static boolean[] visited;

    public static void main(String[] args) throws IOException {
        st = new StringTokenizer(br.readLine());

        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());

        arr = new int[n+1][n+1];
        visited = new boolean[n+1];
        chicken = new int[2];

        for (int i = 1 ; i &lt;= n ; i ++) {
            for (int j = 1 ; j &lt;= n ; j ++) {
                arr[i][j] = Integer.MAX_VALUE;
                if (i == j) arr[i][j] = 0;
            }
        }

        for (int i = 0 ; i &lt; m ; i ++) {
            st = new StringTokenizer(br.readLine());

            int from = Integer.parseInt(st.nextToken());
            int to = Integer.parseInt(st.nextToken());

            arr[from][to] = 1;
            arr[to][from] = 1;
        }

        floyd();

        dfs(0, 1);

        System.out.println(resultX + &quot; &quot; + resultY + &quot; &quot; + min * 2);

    }

    static void floyd() {
        for (int k = 1 ; k &lt;= n ; k++) {
            for (int i = 1 ; i &lt;= n ; i ++) {
                for (int j = 1 ; j &lt;= n ; j ++) {
                    if (arr[i][k] == Integer.MAX_VALUE || arr[k][j] == Integer.MAX_VALUE) continue;
                    if (arr[i][j] &gt; arr[i][k] + arr[j][k]) {
                        arr[i][j] = arr[i][k] + arr[j][k];
                    }
                }
            }
        }
    }

    static void dfs(int depth, int idx) {
        if (depth == 2) {
            int sum = 0;
            int x = -1;
            int y = -1;

            for (int i = 1 ; i &lt;= n ; i ++) {
                // 치킨집 사이의 거리가 짧은것을 선택해서 합연산 하기
                sum += Math.min(arr[i][chicken[0]], arr[i][chicken[1]]);
            }

            if (min &gt; sum) {
                resultX = chicken[0];
                resultY = chicken[1];
                min = sum;
            }

            return;
        }

        for (int i = idx ; i &lt;= n ; i ++) {
            if (!visited[i]) {
                visited[i] = true;
                chicken[depth] = i;
                dfs(depth + 1, i);
                visited[i] = false;
            }
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 활용 2 - 1]]></title>
            <link>https://velog.io/@sh_38/JPA-%ED%99%9C%EC%9A%A9-2-1</link>
            <guid>https://velog.io/@sh_38/JPA-%ED%99%9C%EC%9A%A9-2-1</guid>
            <pubDate>Wed, 01 May 2024 07:49:50 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="섹션-1---api-개발-기본">섹션 1 - API 개발 기본</h2>
<h3 id="1-회원-등록-api">1. 회원 등록 API</h3>
<ol>
<li>@Valid 어노테이션을 붙여서 파라미터 유효성을 검증하자</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_38/post/f68b10aa-8a67-4d04-8899-48212e997560/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/ddc818ea-67e1-4f5c-b23a-ff11f5cc4af4/image.png" alt="">
엔티티 내 @NotEmpty 같이 유효성을 검증해야 하는 컬럼들이 있고
API 요청이 들어오면 들어온 파라미터 값을 검증해주는 역할을 한다.</p>
<blockquote>
<p>@NotEmpty가 없으면 해당 필드 값이 null 이더라도 넘어가진다.</p>
</blockquote>
<ol start="2">
<li>@RequestBody로 들어오는 파라미터에는 엔티티 그대로 받지 않는다.
<img src="https://velog.velcdn.com/images/sh_38/post/60624e14-ad6c-43b0-b2dc-6ae7f1a078a5/image.png" alt=""></li>
</ol>
<p>API 요청 시 Entity의 필드변수 명 그대로 요청 보내야 하는데
나중에 변경이 된다면 Bad Request 발생</p>
<blockquote>
<p>전용 DTO를 만들어서 받아주도록 하자</p>
</blockquote>
<p>전용 DTO로 만들어 주는 방법</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/df3b142f-7576-47b5-ae2f-b4b8b2059d6f/image.png" alt=""></p>
<p>위와 같이 만들었을 때
Entity 내 name이라는 컬럼이 바뀌었을 때
-&gt; 당연히 setName 에서 컴파일 에러가 떠서 에러를 잡을 수 있고
-&gt; CreateMemberRequest 라는 DTO에 컬럼 속성을 지정해서 DTO만 보고 확인할 수 있다.</p>
<blockquote>
<p>실무에서는 <strong>절대</strong> 엔티티를 외부에 노출시키거나 그대로 받으면 안된다.</p>
</blockquote>
<h3 id="2-회원-수정-api">2. 회원 수정 API</h3>
<ol>
<li><p>DTO를 받아서 쓰면 DTO에는 @Data 어노테이션 꼽아서 사용해도 무방</p>
</li>
<li><p>수정할 때는 변경감지(더티체킹)를 사용하도록 하자
<img src="https://velog.velcdn.com/images/sh_38/post/311424aa-e3d3-479c-8fd7-c2504129b48e/image.png" alt=""></p>
</li>
</ol>
<p>수정하기 위한 객체를 가져와서 가져와서 수정해주기</p>
<h3 id="3-회원-조회-api">3. 회원 조회 API</h3>
<ol>
<li>findMembers 할 때 Java8의 stream을 사용하는데
findAll 과의 차이점은 뭐지?</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTPS]]></title>
            <link>https://velog.io/@sh_38/HTTPS-with-apk%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@sh_38/HTTPS-with-apk%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Mon, 29 Apr 2024 04:09:02 GMT</pubDate>
            <description><![CDATA[<h3 id="http-hypertext-transfer-protocol">HTTP (HyperText Transfer Protocol)</h3>
<p>: 인터넷에서 사용하는 텍스트 기반의 데이터 전송 프로토콜</p>
<p>구글에서는 인터넷을 켰을 때
HTTP 프로토콜을 사용하면 <strong>&#39;안전하지 않은 사이트&#39;</strong> 라고 나옴
-&gt; 서버에서 클라이언트로 전송되는 데이터는 암호화 되지 않은채로 보내진다.</p>
<ul>
<li><u>구글에서 권장하는 프로토콜은 HTTPS 이다.</u></li>
</ul>
<h3 id="https-http--secure">HTTPS (HTTP + Secure)</h3>
<p>: HTTP에 보안(SSL: Secure Sockets Layer)을 더한 프로토콜</p>
<p>SSL을 더함으로써 전송되는 데이터가 암호화 되어 정보를 도난당하지 않게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[docker+jenkins+nginx 로 시작하기 2 (CD)]]></title>
            <link>https://velog.io/@sh_38/dockerjenkinsnginx-%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-2-CD</link>
            <guid>https://velog.io/@sh_38/dockerjenkinsnginx-%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-2-CD</guid>
            <pubDate>Tue, 23 Apr 2024 00:50:19 GMT</pubDate>
            <description><![CDATA[<h1 id="1-ssl-발급받기">1. SSL 발급받기</h1>
<p>letsencrypt 를 통해 https 인증서를 받는다.</p>
<pre><code class="language-shell">sudo apt-get install letsencrypt
sudo letsencrypt certonly --standalone -d &lt;도메인&gt;</code></pre>
<p>이 인증서는 90일 유효하므로 기한이 끝나기 전 갱신을 해주거나
certbot을 통해 자동으로 갱신되게 하는 시스템이 있어서 그걸 사용해야 한다고 함
(여기서는 일단 90일 인증서로 진행)</p>
<h1 id="2-jenkins-내-docker-설치">2. Jenkins 내 Docker 설치</h1>
<p>jenkins 컨테이터 접속 후 설치해준다.</p>
<pre><code>docker exec -it jenkins bash</code></pre><pre><code>            apt-get update &amp;&amp; \
            apt-get -y install apt-transport-https \
                ca-certificates \
                curl \
                gnupg2 \
                software-properties-common &amp;&amp; \
            curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - &amp;&amp; \
            add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable&quot; &amp;&amp; \
            apt-get update &amp;&amp; \
            apt-get -y install docker-ce-cli</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[AWS - 서버 초기 설정]]></title>
            <link>https://velog.io/@sh_38/AWS-%EC%84%9C%EB%B2%84-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@sh_38/AWS-%EC%84%9C%EB%B2%84-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 22 Apr 2024 09:01:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-vimrc-설정-해주기">1. .vimrc 설정 해주기</h2>
<pre><code class="language-shell">set hlsearch
set nu
set autoindent
set ts=4
set sts=4
set cindent
set laststatus=2
set shiftwidth=4
set showmatch
set smartcase
set smarttab
set smartindent
set ruler
set fileencodings=utf8,euc-kr
</code></pre>
<h2 id="2-서버-시간-설정하기">2. 서버 시간 설정하기</h2>
<pre><code>sudo timedatectl set-timezone Asia/Seoul
</code></pre><h2 id="3-ufw-포트-열기">3. ufw 포트 열기</h2>
<pre><code>sudo ufw allow 80
sudo ufw allow 3209   // DB 포트 (3306 대신)
sudo ufw allow 8545   // ganache-cli 포트

sudo ufw enable
</code></pre><h2 id="4-카카오-미러서버-설정하기">4. 카카오 미러서버 설정하기</h2>
<pre><code>sudo vim /etc/apt/sources.list</code></pre><p>여기서 서버 주소를 mirror.kakao.com 으로 바꿔준다.</p>
<p>아래쪽에 security.ubuntu.com 도 바꿔 줘야하는듯.</p>
<p>*vim 치환 명령어
:%s/hello/world
-&gt; hello 라는 문자열을 world 라는 문자열로 치환한다.</p>
<p>ex) :%s/security.ubuntu/mirror.kakao</p>
<h2 id="5-apt-update">5. apt update</h2>
<pre><code>sudo apt-add-repository -r ppa:certbot/certbot

sudo apt update
sudo apt-get update
sudo apt-get upgrade</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[docker+jenkins+nginx 로 시작하기 1 (CI)]]></title>
            <link>https://velog.io/@sh_38/dockerjenkinsnginx-%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@sh_38/dockerjenkinsnginx-%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Wed, 17 Apr 2024 08:37:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>순서 대로 작성은 할 것인데 생각의 흐름 대로 적어
글에 두서가 없을 수 있습니다.</p>
</blockquote>
<h1 id="docker-설치">Docker 설치</h1>
<pre><code class="language-shell">sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common</code></pre>
<ul>
<li>apt-transport-https : 패키지 관리자가 https를 통해 데이터 및 패키지에 접근할 수 있도록 한다.</li>
<li>ca-certificates : ca-certificate는 certificate authority에서 발행되는 디지털 서명. SSL 인증서의 PEM 파일이 포함되어 있어 SSL 기반 앱이 SSL 연결이 되어있는지 확인할 수 있다.</li>
<li>curl : 특정 웹사이트에서 데이터를 다운로드 받을 때 사용</li>
<li>software-properties-common : *PPA를 추가하거나 제거할 때 사용한다.</li>
</ul>
<h3 id="docker의-공식-gpg키를-추가">Docker의 공식 GPG키를 추가</h3>
<pre><code class="language-shell">curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -</code></pre>
<h3 id="docker의-공식-apt-저장소를-추가">Docker의 공식 apt 저장소를 추가</h3>
<pre><code class="language-shell">sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;</code></pre>
<h3 id="시스템-패키지-업데이트">시스템 패키지 업데이트</h3>
<pre><code class="language-shell">sudo apt-get update</code></pre>
<h3 id="docker-설치-1">Docker 설치</h3>
<pre><code class="language-shell">sudo apt-get install docker-ce docker-ce-cli containerd.io</code></pre>
<h3 id="docker-설치-시-permission-denied">Docker 설치 시 Permission denied</h3>
<p><img src="https://velog.velcdn.com/images/sh_38/post/2a0e7677-d95b-4a6f-9940-b32ce5765a06/image.png" alt=""></p>
<pre><code class="language-shell">sudo usermod -aG docker [계정명을 기입하시오]
cat /etc/group | grep docker</code></pre>
<p>or</p>
<pre><code>sudo groupadd docker
sudo usermod -aG docker $USER
sudo newgrp docker</code></pre><hr>
<h1 id="ci">CI</h1>
<h2 id="jenkins-설치">jenkins 설치</h2>
<p><strong>CI/CD : Continuous Integration/Continuous Delivery</strong></p>
<p>spring boot 3.2.x 버전의 서버를 돌리므로
jdk17 버전의 jenkins를 사용한다.</p>
<p>추후에 DooD 를 사용하기 위해 jenkins의 볼륨을 마운트 해줘야 하는데 jenkins를 껐다켤때마다 다시 옵션을 설정해줘야 하는 번거로움이 있다.
이를 <strong>모두 기억하기 힘드니까 도커 컴포즈 파일로 만들어서 빌드한다</strong>.</p>
<p>그리고 여러개의 도커 컴포즈 파일을 만들 수 있으므로
composeFiles라는 디렉토리를 만들어서 관리하도록 하고</p>
<p>그 안에 docker-compose.jenkins.yml 을 만들어 준다.</p>
<ul>
<li>docker-compose.jenkins.yml<pre><code>version: &quot;3.8&quot;
</code></pre></li>
</ul>
<p>services:
  jenkins:
    container_name: jenkins
    image: jenkins/jenkins:jdk17
    restart: unless-stopped
    user: root
    ports:
      - 9090:8080
    volumes:
      - /home/ubuntu/jenkins:/var/jenkins_home
      - /home/ubuntu/.ssh:/root/.ssh
      - /var/run/docker.sock:/var/run/docker.sock</p>
<pre><code>
+04.24 추가) 계속 젠킨스 컨테이너 내 도커를 설치했을 때 다음부터 접속이 안되는 문제가 있어서 서버를 몇 번 초기화했다. /home/ubuntu/.ssh:/root/.ssh를 마운트 하면서 진행하는데 이 부분에서 문제가 발생하는 것이 아닌지 의심중 -&gt;

* volumes 설명
1. /home/ubuntu/jenkins
: jenkins에서 코드를 클론하면 /home/ubuntu/jenkins 안에
workspace 라는 곳 안에 들어가게 됨.
2. /home/ubuntu/.ssh
: ubuntu를 외부에서 접속했을 때 root 로 접속이 가능하게 하기 위함
( 이게 필수인지는 잘 모르겠음, 권한 문제 때문에 필요한 것 같음 )
3. /var/run/docker.sock
: jenkins 컨테이너가 docker.sock을 통해 docker host에게 명령을 전달하기 위해 마운트 해주고 사용한다.

추가) - docker.sock
![](https://velog.velcdn.com/images/sh_38/post/99bb2c26-a1ae-4edc-821e-c563d7507cc6/image.png)

docker.sock 은 항상 명령을 받을 준비를 한다. 얘를 컨테이너가 사용하기 위해 볼륨으로 마운트 해주는 것

- jenkins 컨테이너 생성
</code></pre><p>$ docker compose -f dockerFiles/docker-compose.jenkins.yml up -d --build</p>
<pre><code>* 옵션 설명
1. -f : 파일 직접 지정 (설정 안해주면 현재 디렉토리에서 docker-compose.yml 을 빌드함)
2. -d : detached 옵션으로 실행 (백그라운드 에서 실행)
3. -- build : 컨테이너 생성시 이미지를 다시 생성하는 옵션

실행했으면 브라우저에서 접속해보기
-&gt; ```$ docker ps -a``` 로 로그 확인해보고 켜졌는지 확인
-&gt; 안되면 ec2 인스턴스 보안그룹에서 포트 설정 확인하기
-&gt; 안되면 ec2 방화벽 설정 해주기 ```$ sudo ufw status``` 확인해보기

접속했다면
```$ docker logs jenkins``` 를 입력해서
설치를 위한 비밀번호 복사한 후 사이트에 붙여넣고 기본(권장) 설치로 설치하기

- 비밀번호
![](https://velog.velcdn.com/images/sh_38/post/34dcf1d8-ba00-432f-aa2b-952bddb799a7/image.png)


- 설치화면
![](https://velog.velcdn.com/images/sh_38/post/16215701-1404-41d7-9e33-87c153b26f70/image.png)


우리는 CI/CD를 구축하는데
Pipeline으로 구축하겠다
-&gt;
1. 빌드하는데 각 단계별로 모니터링 가능하면서
2. 에러가 어디서 발생했는지 쉽게 볼 수 있고
3. 스크립트를 짜서 관리가 쉽다.

단점 : 스크립트를 짤 줄 알아야 한다.

## jenkins 시작

item을 생성할 때 pipeline으로 생성해준다.

![](https://velog.velcdn.com/images/sh_38/post/6a4bbdb8-b670-4700-9312-6aa49e6c95df/image.png)

Pipeline script 는 웹 페이지 상에서 스크립트를 작성해서 적용시키는 방식이고
밑에 SCM 은 jenkinsfile로 스크립트 명령어를 관리하는 방식을 채택한다는 뜻.

우리는 웹에다 바로 적자

``` node
pipeline {
    agent any

    stages {
        stage(&#39;Clone&#39;) {
            steps{
                git branch: &#39;{변경 감지 할 브랜치}&#39;, credentialsId: &#39;{credential 아이디}&#39;, url: &#39;{web-hook 보내는 git 리포지토리 주소 ex) ~~.git}&#39;           
                sh &quot;sed -i &#39;s/{docker-compose.yml에서 암호화된 JASYPT_KEY}/{암호화 전 키}/g&#39; docker-compose.yml&quot;
                sh &quot;cat docker-compose.yml&quot;
            }

        }
        stage(&#39;Build&#39;){
            steps{
                dir(&#39;backend/farmyo&#39;) {
                    sh &quot;chmod +x gradlew&quot;
                    sh &quot;./gradlew clean build&quot;
                }
            }

        }
        stage(&#39;Deploy&#39;) {
            steps{
                // 이전에 실행된 컨테이너를 중지하고 삭제합니다.
                sh &quot;docker stop farmyo-backend || true&quot;
                sh &quot;docker rm farmyo-backend || true&quot;

                sh &quot;docker stop farmyo-frontend || true&quot;
                sh &quot;docker rm farmyo-frontend || true&quot;

                // 이전에 빌드된 이미지를 삭제합니다.
                sh &quot;docker rmi farmyo_pipeline-farmyo-frontend || true&quot;
                sh &quot;docker rmi farmyo_pipeline-farmyo-backend || true&quot;

                sh &quot;docker ps -a || true&quot;
                sh &quot;docker images || true&quot;

                sh &quot;docker compose build --no-cache&quot;
                sh &quot;docker compose up -d&quot;
            }
        }
    }
    post {
        success {
            script {
                def Author_ID = sh(script: &quot;git show -s --pretty=%an&quot;, returnStdout: true).trim()
                def Author_Name = sh(script: &quot;git show -s --pretty=%ae&quot;, returnStdout: true).trim()
                mattermostSend (color: &#39;good&#39;, 
                message: &quot;빌드 성공 ^_^ v : ${env.JOB_NAME} #${env.BUILD_NUMBER} by ${Author_ID}(${Author_Name})\n(&lt;${env.BUILD_URL}|Details&gt;)&quot;, 
                endpoint: &#39;{deploy 알림을 받을 Mattermost 채널 주소}&#39;, 
                channel: &#39;{알림 받을 채널 명}&#39;
                )
            }
        }
        failure {
            script {
                def Author_ID = sh(script: &quot;git show -s --pretty=%an&quot;, returnStdout: true).trim()
                def Author_Name = sh(script: &quot;git show -s --pretty=%ae&quot;, returnStdout: true).trim()
                mattermostSend (color: &#39;danger&#39;, 
                message: &quot;빌드 실패 ㅜ_ㅜ : ${env.JOB_NAME} #${env.BUILD_NUMBER} by ${Author_ID}(${Author_Name})\n(&lt;${env.BUILD_URL}|Details&gt;)&quot;, 
                endpoint: &#39;{deploy 알림을 받을 Mattermost 채널 주소}&#39;, 
                channel: &#39;{알림 받을 채널 명}&#39;
                )
            }
        }
    }
}</code></pre><h3 id="credentials">Credentials</h3>
<p>jenkins 에서는 git에 수정사항을 Webhook을 통해서 감지하게 되는데
여기서 해당 repository 의 코드를 받아와야 한다.</p>
<p>그러기 위해서는 git 접근 권한을 줘야 한다.
-&gt; repository가 public 이면 굳이 권한까지 필요는 없다고 함</p>
<p>그래도 일단 적용 해보자 ( private로 할 수 있으니까 )</p>
<hr>
<h3 id="github-에서">GitHub 에서</h3>
<ol>
<li>git 에서 프로필 누르면 나오는 settings -&gt; Developer settings
<img src="https://velog.velcdn.com/images/sh_38/post/78c1ccf5-2ca4-47e3-b4be-274d7cf92c11/image.png" alt=""></li>
<li>generate new token</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_38/post/6ea777b0-b122-4b29-b5f9-f7cacbf54cdf/image.png" alt=""></p>
<ol start="3">
<li>read only로 토큰 생성 ( 날짜는 알아서 )
<img src="https://velog.velcdn.com/images/sh_38/post/86e1698f-d373-49d5-8d53-3548f76be6d7/image.png" alt=""></li>
</ol>
<p>해당 토큰 값을 복사해둔다. <strong>중요!!</strong></p>
<hr>
<p><strong>jenkins 에서</strong></p>
<ol>
<li>jenkins 관리 -&gt; Credentials -&gt; system
<img src="https://velog.velcdn.com/images/sh_38/post/76473434-3114-4136-ad6e-93fbce9d11f9/image.png" alt=""></li>
<li>global Credentials -&gt; add Credentials
<img src="https://velog.velcdn.com/images/sh_38/post/3df29f98-1334-4e94-b572-c348a6e53b4f/image.png" alt=""></li>
<li>값 넣고 생성하기
<img src="https://velog.velcdn.com/images/sh_38/post/3e4a7e40-e0e5-44d4-835b-553e8d7af7f2/image.png" alt="">
kind는 &#39;Secret Text&#39;
Secret 은 아까 깃에서 복사해둔 토큰값
ID는 변수명 알아서
후 생성</li>
</ol>
<hr>
<p>++ 추가 <img src="https://velog.velcdn.com/images/sh_38/post/0dec7f23-3285-412d-80bd-ae529d9f5f10/image.png" alt="">
나는 이 플러그인이 안깔려 있어서 파이프 라인이 GUI 로 안보였음
jenkins에서 설치 안되어있으면 설치하시길</p>
<hr>
<h3 id="webhook-설정">webhook 설정</h3>
<p>webhook 용 토큰을 repo, repo_hook 옵션을 체크 해주면서 하나 만들어 주자
<img src="https://velog.velcdn.com/images/sh_38/post/47989232-4680-40d6-933b-542e26291686/image.png" alt=""></p>
<p>그리고 jenkins에 credentials로 등록해준다.</p>
<ul>
<li><p>webhook token 등록
<img src="https://velog.velcdn.com/images/sh_38/post/ebd7c1f8-6602-41bb-9a73-28716e91f438/image.png" alt="">
여기서 password 는 webhook 토큰 생성시 나오는 값 복붙하기.</p>
</li>
<li><p>jenkins 환경설정에서 github server 등록해주기
<img src="https://velog.velcdn.com/images/sh_38/post/f187536a-1b30-42b2-9e5a-8853ee1eba0e/image.png" alt=""></p>
</li>
<li><p>프로젝트에서 webhook 을 받도록 설정
<img src="https://velog.velcdn.com/images/sh_38/post/888a928a-54fa-4ad5-84ed-3840a54faf33/image.png" alt=""></p>
</li>
</ul>
<p>webhook을 보낼 repository에 들어가서 
<img src="https://velog.velcdn.com/images/sh_38/post/56989506-6cba-4665-a07c-38b4e4524adc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/73f92359-4709-423e-91dc-af5b09d3585a/image.png" alt=""></p>
<p>이렇게 설정 해주면
<img src="https://velog.velcdn.com/images/sh_38/post/35bb16e5-84a2-4a26-8388-62ae8785d987/image.png" alt=""></p>
<p>초록색 체크 표시가 뜬다. 그러면 CI 구축 완료</p>
<hr>
<p>+) 추가</p>
<h3 id="gitlab-에서">GitLab 에서</h3>
<p>Personal Access Token 생성은 여기서 해준다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/ac866cbd-4e93-4e64-84c9-195a919fcf2b/image.png" alt=""></p>
<p>Project Access Token은 이렇게 생성한다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/c9b35e16-e0e8-49a7-9edf-b8ef1bfd570b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/be93c8e3-a348-4dd2-a7f8-4145d7d0df18/image.png" alt=""></p>
<p>기한은 2달정도로 잡고 권한은 일단 필요해 보이는거 선택했다.</p>
<h3 id="webhook-설정-1">webhook 설정</h3>
<p><img src="https://velog.velcdn.com/images/sh_38/post/999e80ce-200b-4771-a202-6201831c66e6/image.png" alt=""></p>
<p>jenkins 에서 gitlab 플러그인을 설치한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Solidity - 1]]></title>
            <link>https://velog.io/@sh_38/Solidity-1</link>
            <guid>https://velog.io/@sh_38/Solidity-1</guid>
            <pubDate>Fri, 23 Feb 2024 04:51:13 GMT</pubDate>
            <description><![CDATA[<h2 id="솔리디티">솔리디티</h2>
<hr>
<p>&quot;Solidity에 대한 개념을 제가 이해한 방식으로 풀어 쓴 글입니다.&quot;</p>
<hr>
<h3 id="스마트-컨트랙트">스마트 컨트랙트</h3>
<p>백엔드 개발을 해보고 나서 바로 블록체인 공부를 하게 되었습니다.
이 블록체인의 스마트 컨트랙트를 보고 느낀점
<br></p>
<ol>
<li>백엔드 서버를 블록체인 블록으로 관리한다.</li>
<li>서버를 최대한 효율적으로 짜야 한다.</li>
</ol>
<br>

<p>API를 호출하고 데이터를 받으려면 서버는 항상 켜져있어야 함
반면 블록체인은 컨트랙트를 블록체인에 등록함으로써 블록체인 서버는 항상 켜져있고 거기에 내 API를 추가하는 느낌</p>
<br>

<p>그래서 켜져있는 서버에 API를 등록하고 사용하려면
AWS를 빌려서 <strong>서버비를 내는것처럼 서버 사용료</strong>를 내야겠죠?</p>
<blockquote>
<p>그 사용료를 gas라고 하자</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sh_38/post/1f20c7ec-c3c4-46eb-92d2-e4ef9d6d24d9/image.png" alt=""></p>
<h5 id="1-eth이더리움--109-gwei-가스웨이--1018-wei웨이">1 eth(이더리움) = 10^9 Gwei (가스웨이) = 10^18 wei(웨이)</h5>
<h5 id="1-gas--1-gwei">1 gas = 1 Gwei</h5>
<br>

<ul>
<li>그럼 이 gas는 어떻게 책정되는가?</li>
</ul>
<p>AWS ec2 서버도 한정적인 리소스가 있고 그 리소스를 사용하는데 공평하게 사용하기 위해 프로세스들이 나눠서 돌아가며 사용하는데</p>
<p>모든 사람들이 이 블록체인 서버를 공평하게 사용하기 위해서는 리소스를 사용하는 양에 맞추어 gas를 매겨야 합니다.</p>
<p><a href="https://ethereum.github.io/yellowpaper/paper.pdf">Ethereum Yellow Paper</a></p>
<p>그 기준은 위 논문 27페이지 쯤에 나와있습니다.</p>
<p><u>컨트랙트에서 어떤 행동을 하느냐에 따라 얼마의 gas가 부과되고</p>
<p>해당 함수가 자원을 얼마나 사용하나에 따라 gas가 부과 되는 구조입니다.</u></p>
<hr>
<h3 id="즉">즉</h3>
<p>우리는 컨트랙트의 함수를 실행하는데 드는 비용 (gas)를 최소한으로 하기 위해</p>
<p>함수를 만들때 부터 <strong>딱 필요한 범위 내에서, 딱 필요한 기능만 동작하게</strong> 만들어야 합니다.</p>
<hr>
<p>솔리디티는 function에서 사용되는 키워드 하나하나 마다 책정되는 gas가 다름</p>
<ul>
<li><p>접근제어자</p>
</li>
<li><p>view, pure, payable</p>
</li>
<li><p>사용되는 파라미터 변수의 존재 범위</p>
</li>
</ul>
<p>gas를 사용하는 범위를 세세하게 나눠 두었기 때문에
변수에 접근제어자 선택 안해주고 그냥 아무렇게나 변수 사용하면 난방비 엄청 나올 준비 하셔야 합니다.</p>
<hr>
<ul>
<li>내가 볼려고 정리한 것들</li>
</ul>
<p>external vs public
external 키워드의 경우, 컨트랙트의 외부에서만 함수의 호출이 가능하게 합니다. 반면 public 키워드의 경우 컨트랙트의 내부와 외부 모두에서 함수의 호출이 가능하게 합니다.</p>
<br>

<p>internal vs private
internal 키워드의 경우, 컨트랙트 내부 또는 해당 컨트랙트를 상속하는 컨트랙트에서 호출이 가능합니다. 다른 언어의 protected 키워드와 유사하다고 생각하시면 됩니다. 반면 private 키워드의 경우, 컨트랙트 내부에서만 호출이 가능합니다.</p>
<br>


<p>view : function 밖의 변수들을 읽을수 있으나 변경 불가능
pure : function 밖의 변수들을 읽지 못하고, 변경도 불가능
viwe 와 pure 둘다 명시 안할때: function 밖의 변수들을 읽어서, 변경을 해야함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis]]></title>
            <link>https://velog.io/@sh_38/Redis</link>
            <guid>https://velog.io/@sh_38/Redis</guid>
            <pubDate>Tue, 23 Jan 2024 14:40:07 GMT</pubDate>
            <description><![CDATA[<hr>
<p><img src="https://velog.velcdn.com/images/sh_38/post/a41225eb-5ac2-4369-a834-9c2efdb4cdb4/image.png" alt=""></p>
<h2 id="캐시-메모리">캐시 메모리</h2>
<p>프로세스가 동작할 때는 하드디스크에서 데이터를 가져와서 메인 메모리(RAM) 에 올려놓고 사용합니다.</p>
<p>메인 메모리가 하드디스크 보다 빠르기 때문에 올려두고 빠르게 접근하기 위함입니다.</p>
<p>그런데 여기서 CPU를 더 효율적으로 사용하기 위해 CPU가 접근하기 쉬운 공간을 하나 마련해둡니다.</p>
<p>그것을 <u><strong>캐시 메모리</strong></u> 라고 합니다.</p>
<p><strong>자주 사용하는 데이터</strong>를 쌓아두고 있다가 필요하면 바로바로 꺼내주는 역할을 합니다.</p>
<p>캐시 메모리는 속도가 빠른만큼 가격이 비쌈 =&gt; 적은 용량을 효율적으로 사용해야 함</p>
<br>

<hr>
<br>

<h2 id="redis-remote-dictionary-server">Redis (Remote Dictionary Server)</h2>
<p>서버에서 1만명이 DB의 &#39;싸피&#39; 라는 데이터가 필요하다고 할 때 </p>
<p>서버에서는 이 데이터를 꺼내 보여주기 위해 1만번의 조회가 발생합니다.</p>
<p>하지만 &#39;싸피&#39; 라는 데이터를 캐시 메모리에 올려놓는다면 한번의 DB 연결로 캐시 메모리에 &#39;싸피&#39;를 올려두고 빠르게 데이터를 제공할 수 있겠죠</p>
<p>이 역할을 하는것이 Redis 입니다.</p>
<p>Redis는</p>
<ol>
<li>In Memory 에 데이터를 저장하여</li>
<li><u><strong>자주 사용하지만 변경되지 않는 데이터</strong></u>에게</li>
<li>빠르게 접근하기 위한 서버의 캐시메모리
입니다.   </li>
</ol>
<br>

<hr>
<br>

<h3 id="redis의-장단점">Redis의 장단점</h3>
<p><strong>장점</strong></p>
<ol>
<li><p>DB 접근을 줄여줄 수 있다.</p>
</li>
<li><p>No-Sql로 Key: value 쌍으로 접근할 수 있다. (RDBMS 보다 빠르다고 함)</p>
</li>
<li><p>문법적으로 사용하기 쉽다.</p>
</li>
</ol>
<p><strong>단점</strong></p>
<ol>
<li><p>DB에서 데이터를 직접 가져오는것이 아니기 때문에 DB와 다를 수 있다. 즉 <u><strong>자주 변경되는 데이터를 사용하면 데이터가 불일치 되는 경우가 발생할 수 있음</strong></u></p>
</li>
<li><p>휘발성 메모리에 저장하기 때문에 서버를 끄면 데이터가 사라짐</p>
</li>
</ol>
<p><strong>사용처</strong></p>
<ol>
<li><p>일반적으로 사용자의 세션 관리</p>
<ul>
<li>사용자의 세션을 유지하고, 관리하고, 여러 활동들을 편리하게 추적할 수 있다고 합니다. Refresh 토큰을 발급한걸 가져오면 좋을 듯 합니다.
<a href="https://inkyu-yoon.github.io/docs/Language/SpringBoot/RefreshToken">토큰 관리 관련 블로그</a></li>
</ul>
</li>
<li><p>메세지 큐잉</p>
<ul>
<li>속도가 매우 빨라서 실시간 채팅에 사용이 가능하다고 합니다. <u>대화 내용을 저장하지 않는 경우</u>에 사용하기에 좋아보입니다.
<a href="https://velog.io/@leeseunghee00/Spring-%EC%B1%84%ED%8C%85-%EA%B5%AC%ED%98%84-STOMP-Redis-PubSub">채팅 관련 블로그</a></li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[PuTTY - no supported authentication methods available (server sent publickey gssapi-keyex gssapi-with-mic)]]></title>
            <link>https://velog.io/@sh_38/PuTTY-no-supported-authentication-methods-available-server-sent-publickey-gssapi-keyex-gssapi-with-mic</link>
            <guid>https://velog.io/@sh_38/PuTTY-no-supported-authentication-methods-available-server-sent-publickey-gssapi-keyex-gssapi-with-mic</guid>
            <pubDate>Mon, 15 Jan 2024 12:23:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>no supported authentication methods available (server sent publickey gssapi-keyex gssapi-with-mic)</p>
</blockquote>
<p>다른 블로그에서 시킨 대로 PuTTY로 SSH 접속을 시도했는데 계속 위 에러가 발생했다.</p>
<h1 id="해결-방법">해결 방법</h1>
<p>계속 안되다가 이 두가지 방법을 하고 나서 접속이 가능했음.</p>
<ol>
<li>AWS 에서 인스턴스를 다른 인스턴스로 생성하기.
(이전에는 Amazon Linux 2023 AMI (처음에 선택 되어있음) 을 사용 함.)</li>
</ol>
<p>-&gt; Amazon Linux 2 AMI 저걸로 인스턴스 생성 했음.</p>
<pre><code>![](https://velog.velcdn.com/images/sh_38/post/21a9de61-b850-4509-bb68-40d07d73faec/image.png)</code></pre><p><img src="https://velog.velcdn.com/images/sh_38/post/5e60dcee-4a6e-4c21-81ab-e3eea915e53f/image.png" alt=""></p>
<ol start="2">
<li>PuTTY 를 최신 버전으로 다시 설치함</li>
</ol>
<p>-&gt; 혹시나 싶어 업데이트를 했는데 PuTTY 설정 화면이 조금 다름.
업데이트는 해주는게 좋을 듯.</p>
<hr>
<ol>
<li>구글 찾아보면 무슨 .ssh 파일 접근 권한을 설정해줘야 한다고 해서 설정 해줬는데, 안되고</li>
<li>서버안에 authentication파일 안에 퍼블릭 키를 넣어줘야 한다고 해서 넣어주고 해도 안되고</li>
<li>/etc/bin/ssh? 무슨 로컬 파일 경로에 passAuthentication 머시기?
주석 풀고 해보라길래 해봐도 안되고 
아무리 해봤는데도 안됐음...</li>
</ol>
<hr>
<h1 id="추가">추가</h1>
<p> 01.15 -&gt; PuTTY 가 문제였는것으로 보임</p>
<p>&#39;Amazon 2023 AMI&#39; 을 사용해도 문제가 없음</p>
<p>만약 위 에러가 계속 발생한다면 아래의 해결 방식을 따라보는 것을 추천함.</p>
<h2 id="1-ec2를-새로-생성한다">1. ec2를 새로 생성한다.</h2>
<p>새로 생성하면서 key를 .pem 으로 새로 생성하고 파일을 저장한다.
만약 현재 서버에 뭐가 많아서 서버를 지우기 좀 그렇다면
--&gt; 키를 생성해서 해당 인스턴스에 다시 등록해줘야 한다.
인스턴스를 생성할 때 마다 key가 등록이 되는데 이 키는 <strong>생성할 때 받아두는 방법 밖에 없어서</strong> 그 key 파일을 잃어버렸다면 새로운 키를 발급 받아 직접 인스턴스에 등록해야 한다.</p>
<p>key를 재발급해서 등록하는것은 찾아보길 바람.</p>
<h2 id="2-pem-키를-ppk-키로-바꿔준다">2. .pem 키를 .ppk 키로 바꿔준다.</h2>
<p>ec2 생성시 받는 key 페어는 .pem으로 받아서 .ppk로 바꿔줘야 한다.
물론 생성할 때 .ppk로 (for PuTTY) 인가 이렇게 옵션이 있을텐데
이걸 사용하니까 안 됨.</p>
<p>PuTTY에서는 이 키가 옛날 형식의 키라고 뜨면서 거부함.</p>
<p>그래서 PuTTYgen 을 통해서 .ppk 키를 만들고 사용해줍시다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS - 3. ec2 및 RDS 생성하기]]></title>
            <link>https://velog.io/@sh_38/AWS-3.-ec2-%EB%B0%8F-RDS-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sh_38/AWS-3.-ec2-%EB%B0%8F-RDS-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 15 Jan 2024 12:08:52 GMT</pubDate>
            <description><![CDATA[<h1 id="ec2-인스턴스-생성">ec2 인스턴스 생성</h1>
<p>ec2 인스턴스 생성은 구글에 쳐보면 생성하는 방법이 다 나와있습니다.
잘 따라 해보시면 됩니다.</p>
<p>생성 도중에 놓칠 수 있는 부분에 대해서 설명하자면</p>
<h2 id="1-aws-ec2를-생성할-때-생성하는-pem-형식의-key를-꼭-받아두세요">1. aws ec2를 생성할 때 생성하는 .pem 형식의 key를 꼭 받아두세요..!</h2>
<p><img src="https://velog.velcdn.com/images/sh_38/post/27d81989-f454-4a33-a259-50179d43431f/image.png" alt=""></p>
<p>인스턴스 생성 시 &#39;키 페어&#39; 를 생성하고 등록하는 과정이 있는데 <strong>&#39;새 키 페어 생성&#39;</strong> 하시고 생성된 키 페어를 꼭 저장하신 후 생성하세요</p>
<p>이 .pem 키를 받아서 PuTTYgen 이라는 .ppk 생성 프로그램으로 PuTTY 전용 key를 생성해서 사용하게 됩니다.
(ec2 생성할 때 바로 .ppk 키를 받아서 사용하면 인증 방식이 구버전이라고 사용이 불가능한 이슈가 있었습니다.)</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/bb62a765-3f1a-432a-81c2-861309c964bb/image.png" alt=""></p>
<h2 id="2-볼륨-크기는-30gb-까지-무료입니다">2. 볼륨 크기는 30GB 까지 무료입니다.</h2>
<p>프리티어를 사용하신다면 볼륨 크기가 30GB까지 무료이기 때문에 이를 적극 사용하시기 바랍니다.
<img src="https://velog.velcdn.com/images/sh_38/post/118b2f10-b4ad-4e5b-ba39-f1edd4ef828a/image.png" alt=""></p>
<p>1 x 30GiB로 설정하면 됨.</p>
<hr>
<h1 id="rds-서버-생성">RDS 서버 생성</h1>
<p>RDS 또한 생성하는 것은 구글에서 검색하면 다 나오기 떄문에</p>
<p>여기서는 RDS 생성시 설정한 내용들과 몇가지 <u><strong>주의사항</strong></u>을 짚고 넘어가겠습니다.</p>
<p>RDS는 나도 모르게 과금 될 수 있는 설정이 몇가지 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/845f7f5c-a2d3-4631-acba-561c5692e79c/image.png" alt=""></p>
<p>*<a href="https://lee-automation-lab.tistory.com/entry/AWS-RDS-%EB%8B%A4%EC%A4%91AZ%EC%9D%B4%EB%9E%80ActiveStandby-%EC%99%80-MasterSlave"> 다중 AZ 설명</a>
(요약 설명하자면 DB 서버에 에러가 발생했을 시, 이를 대처하기 위한 또 다른 서버를 한대 더 가동하고 있는 것) -&gt; 프리티어에서는 안된다고 했는데 체크해버리면 과금될 위험이 있을지도...?</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/4ef48760-6207-4057-982b-52ba36cafdd2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/75fcfa25-93e1-4605-9caf-7497ffff9a9d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/617da552-f20a-4fe6-950b-c8b6c78b3a37/image.png" alt=""></p>
<p>스토리지 자동 조정이 할당된 용량을 벗어나면 알아서 늘려버리는 옵션입니다.
나도 모르게 돈이 나가는 불상사를 막기 위해 체크를 풀어줍니다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/8c624bb5-9874-4430-aaab-35a1b480952e/image.png" alt=""></p>
<p>퍼블릭 액세스는 풀어서 workbench 에서도 연결이 가능하게 해줍니다.
&#39;아니요&#39; 를 체크하면 VPC 내에서만 사용이 가능하게 되는데
VPC란 amazon Virtual Private Cloud의 약자로 아마존 인스턴스들 관계에서만 사용이 가능합니다. <u><strong>즉 ec2로만 접근하게 된다는 말.</strong></u></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/f6ee5278-5e89-4a52-a981-1d81e40d1bb6/image.png" alt=""></p>
<p>위 옵션은 필요한 사람들은 사용하면 됩니다.</p>
<p>마이너 버전 자동 업그레이드 --&gt; 몇달에 한번씩 취약점 보완을 위한 마이너 버전에 대한 업데이트가 발생함. --&gt; 몇분 길면 한시간 동안 db 사용이 불가능해진다는 점이 있음.</p>
<hr>
<p>각 인스턴스 생성 후 
ec2와 RDS 연결은 같은 VPC/보안 그룹 환경에 ec2와 RDS를 넣어주면 됩니다.
<br><br>
ec2와 RDS 생성 시 놓칠 수 있는 부분에 대해서 다루어 보았습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS - 2. ec2 인스턴스를 생성하기에 앞서]]></title>
            <link>https://velog.io/@sh_38/%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%83%9D%EC%84%B1-%EB%B0%8F-PuTTY-%EC%A0%91%EC%86%8D</link>
            <guid>https://velog.io/@sh_38/%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%83%9D%EC%84%B1-%EB%B0%8F-PuTTY-%EC%A0%91%EC%86%8D</guid>
            <pubDate>Fri, 12 Jan 2024 01:37:35 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="ec2-인스턴스를-생성하기에-앞서">ec2 인스턴스를 생성하기에 앞서...</h3>
<p>우리는 free tier를 사용하기 때문에 t2.micro 컴퓨터를 사용하는데
얘는 기본적으로 Linux 운영체제가 탑재되어 나옵니다. (Linux 는 오픈소스라 무료임)</p>
<p>그렇기에 우리는 Linux를 사용할 줄 알아야 하겠죠. 맞죠?</p>
<p>Linux는 Shell Script로 사용을 할 수 있습니다.</p>
<p><br><br></p>
<h3 id="windows-에서는">Windows 에서는</h3>
<p>마우스를 움직여서 메모장을 켜고, 내용을 입력을 하고, 마우스로 저장 버튼을 누르거나 Ctrl+S를 해서 저장하고, 저장된 .txt 파일을 더블클릭으로 켜는 과정들을
<u><strong>Linux에서는 Shell 환경에서 명령어를 입력해서 수행</strong></u>해야합니다.
<br>
ex)</p>
<ul>
<li>pwd  :  사용자가 현재 들어가있는 폴더(디렉토리)의 위치를 보여줌 - present working directory 의 약자</li>
<li>ls : 현재 폴더(디렉토리) 안에 어떤 파일들이 있는지 보여줌 - list의 약자</li>
</ul>
<p><br><br></p>
<hr>
<h2 id="ssh">SSH</h2>
<p>Secure SHell 의 약자 
22번 포트를 사용합니다.</p>
<p>다른 컴퓨터의 쉘을 사용하게 해 주는 <strong>프로그램 혹은 프로토콜</strong>을 말합니다.</p>
<p>Shell에 접근하는데 들어갈 수 있는 사람들을 제한하기 위해
Shell에 들어가려면 Key 가 필요한 시스템임</p>
<p>Key는 파일 형태로 넣어줍니다.</p>
<br>
일반적으로 SSH 환경에 접속하는데에는 Windows 에서는 
PuTTY 라는 원격 접속 프로그램을 통해 접속하게 됩니다.

<hr>
<h1 id="putty">PuTTY</h1>
<p><img src="https://velog.velcdn.com/images/sh_38/post/5159d17f-f350-4b11-a0fc-7a08c9b54ac3/image.png" alt=""></p>
<p>PuTTY는 이런식으로 생겼고
위의 HostName 에는 접속하고자 하는 컴퓨터의 IP주소를 넣어줍니다.</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/7b8f0de6-4be0-490f-9aab-cae2cba227cc/image.png" alt=""></p>
<p>그리고 Auth 탭에 가면 접속에 필요한 Key 파일을 넣어주고 Open 하면 접속이 가능합니다.</p>
<hr>
<p>여기까지는 SSH와 PuTTY에 관한 설명이고</p>
<p>다음에는 ec2 인스턴스를 생성해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS - 1. AWS에 대한 기본적인 개념]]></title>
            <link>https://velog.io/@sh_38/AWS%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EC%8B%9C%EC%9E%91%ED%95%98</link>
            <guid>https://velog.io/@sh_38/AWS%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EC%8B%9C%EC%9E%91%ED%95%98</guid>
            <pubDate>Sun, 07 Jan 2024 14:47:52 GMT</pubDate>
            <description><![CDATA[<h1 id="aws-amazon-web-service">AWS (Amazon Web Service)</h1>
<p>클라우드 서비스 플랫폼이다.</p>
<p>서버를 구축하는데 필요한 여러 장치들을 클라우드 환경에서 제공해주는 서비스</p>
<p><br><br>
서버를 구동하기 위해서는</p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/dfd79a20-8253-4ff9-839e-00c424474ce8/image.png" alt=""></p>
<ul>
<li>본인의 컴퓨터를 서버로 사용하기</li>
<li>가상의 컴퓨터를 서버로 사용하기</li>
</ul>
<p>두 가지의 방법이 존재하는데</p>
<p>전자를 온 프레미스(on-premise), 후자를 클라우드 방식이라고 한다.</p>
<h2 id="우리가-클라우드-서비스를-사용하는-이유">우리가 클라우드 서비스를 사용하는 이유?</h2>
<ol>
<li>서버는 비싸다<ul>
<li>직접 돈주고 환경을 맞추려면 비쌈</li>
</ul>
</li>
<li>보안을 대신 책임져준다<ul>
<li>아마존은 대기업인 만큼 보안을 철저히 관리한다</li>
</ul>
</li>
<li>필요한 리소스를 유동적으로 사용할 수 있다<ul>
<li>많은 용량이 필요한게 아니라면 용량을 줄일 수 있고 갑자기 많이 필요해지면 더 사용할 수 있다</li>
</ul>
</li>
</ol>
<p>이러한 장점들이 있으므로 기업에서도 많이 사용한다.</p>
<br>

<hr>
<br>

<h1 id="aws-에서-제공하는-서비스">aws 에서 제공하는 서비스</h1>
<p>&lt;출처&gt; - <a href="https://medium.com/@heizence6626/aws-%EC%9D%98-%EA%B8%B0%EB%B3%B8-%EC%84%9C%EB%B9%84%EC%8A%A4-s3-ec2-rdb%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-1eb60cbd951d">AWS 의 기본 서비스 S3, EC2, RDS에 대하여</a></p>
<h2 id="1-s3-simple-storage-service">1. S3 (Simple Storage Service)</h2>
<p><img src="https://velog.velcdn.com/images/sh_38/post/b747ac3c-2669-4763-828a-16e9946a62a5/image.png" alt=""></p>
<p>데이터를 저장하거나 꺼내오는 서버임</p>
<p>S3 는 <strong>버킷</strong>이라는게 있다.
버킷에는 key와 value의 형태로 데이터가 저장되는데 하나의 value(object)는 하나의 key를 갖는다.
이 value는 실제 데이터가 저장되는 파일 부분과 객체의 메타데이터(생성일, 크기, 유형)로 구성된다.</p>
<pre><code>key - obj{ file + metaData }</code></pre><h3 id="버킷">버킷</h3>
<ul>
<li>버킷은 S3에서 생성할 수 있는 최상위 디렉토리로 Region 별로 생성이 가능하며</li>
<li>버킷의 이름은 모든 Region에서 <strong><u>유일</u></strong> 해야 한다.</li>
<li>버킷은 계정 별 100개까지 생성이 가능하다.</li>
</ul>
<p>데이터에 대한 접근은
<code>http://{버킷명}.S3.amazonaws.com/{객체키}</code></p>
<p>ex) photo 라는 버킷이 있고 그 안에 animal/dog.jpg 라는 key를 가진 사진 파일이 있을 수 있다.
-&gt; <code>http://photo.S3.amazonaws.com/animal/dog.jpg</code>
로 데이터에 대한 접근이 가능하다.</p>
<p>사진, 동영상 </p>
<br>

<h3 id="특징">특징</h3>
<ol>
<li><p>S3에는 저장할 수 있는 <strong><u>파일 수 및 용량의 제한이 없다</u></strong> (물론 쓴 만큼 비용을 냄)
다만 각 객체의 크기는 최소 0Byte에서 5TB까지 이다.
하나의 PUT 요청으로 업로드 가능한 객체의 크기는 5GB 이다.</p>
</li>
<li><p>모든 PUT, DELETE (삽입, 삭제) 요청에 대해 read-after-write(쓰기 후 읽기) 정책을 기본으로 한다.
그래서 GET, LIST (읽기) 요청은 모든 데이터가 수정이 완료 된 후 이루어 지게 되므로 <strong><u>일관성이 보장된다.</u></strong></p>
</li>
<li><p>Lock 기능을 제공하지 않아 여러 쓰레드에서 동시에 같은 객체에 접근할 경우 문제가 생길 수 있다.
( + 객체에 대해 Transaction 처리를 지원하지 않는다. -&gt; 개별 객체에 대한 원자성만 보장한다. )</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_38/post/f14dc52f-f308-41b1-8ac0-c29d740722d6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sh_38/post/0b7ecfeb-ca72-4fac-88bb-f54a9f1027a9/image.png" alt=""></p>
<br>

<h2 id="2-ec2-elastic-computed-cloud">2. EC2 (Elastic Computed Cloud)</h2>
<p><img src="https://velog.velcdn.com/images/sh_38/post/3333d2b8-c4aa-4352-ab8b-0fdb5477df22/image.png" alt=""></p>
<br>
가상의 컴퓨터를 빌려주는 서비스 이다.

<p>인터넷 환경에서 저 멀리있는 컴퓨터를 빌려서 원격으로 서버를 운용 해주는 서비스임.</p>
<p>가상의 컴퓨터에 운영체제를 설치하고, 프로그램을 설치해서 서버를 구축할 수 있다.
여기서는 하나의 컴퓨터를 <strong><u>인스턴스</u></strong> 라고 표기한다.</p>
<p>ec2를 사용하는 이유?</p>
<ol>
<li><p><strong>저렴하다.</strong></p>
</li>
<li><p>사용자가 인스턴스를 완전히 제어할 수 있다.</p>
</li>
<li><p>보안을 aws 에서 관리해준다.</p>
</li>
<li><p>내가 사용하고자 하는 서버의 용량을 탄력적으로 늘리거나 줄일 수 있다.</p>
</li>
</ol>
<h2 id="3-rds-relational-database-service">3. RDS (Relational Database Service)</h2>
<p><img src="https://velog.velcdn.com/images/sh_38/post/73802b58-66b1-4d34-a76e-89f33a752eb5/image.png" alt=""></p>
<p>RDB (Relational Database) 를 간편하게 운영, 설정, 확장할 수 있게 해주는 서비스이다.</p>
<p>EC2 내부에 MySQL을 설치해서 EC2 내부에서 사용할 수도 있지만,
보통 DB를 위한 별도 RDS를 만들어서 작업한다고 한다. DB 서버를 따로 분리하는 개념.</p>
<p>RDS는 여러가지 RDBMS를 지원하기 때문에 주로 사용하는 DBMS를 사용하면 된다.
(ex : MySQL, oracle, MariaDB, Postgre)</p>
<p>비용이 발생하는 서비스인 만큼 굳이 사용을 안해도 상관은 없으나
사용을 하면 DB서버를 따로 분리하여 데이터 손실이나, 유지에 대한 부담을 덜 수 있다.</p>
<p>결국 서비스의 유지보수 및 속도 측면에서 어떤것이 좋을지 생각해 볼 수 있다.</p>
]]></description>
        </item>
    </channel>
</rss>