<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>emong_96.log</title>
        <link>https://velog.io/</link>
        <description>코린이 입니당 :)</description>
        <lastBuildDate>Wed, 04 Jan 2023 13:45:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>emong_96.log</title>
            <url>https://velog.velcdn.com/images/emong_96/profile/0415517a-e7e4-4b84-b346-7537a245d16c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. emong_96.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/emong_96" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Daily_Study_Checker(2) - SENSE 사용, Server 생성]]></title>
            <link>https://velog.io/@emong_96/DailyStudyChecker2-SENSE-%EC%82%AC%EC%9A%A9-Server-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@emong_96/DailyStudyChecker2-SENSE-%EC%82%AC%EC%9A%A9-Server-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Wed, 04 Jan 2023 13:45:29 GMT</pubDate>
            <description><![CDATA[<h2 id="sense-사용">SENSE 사용</h2>
<p>NCP설정은 저번에 해주었으니 이번에는 SENSE가 잘 작동되는지 테스트 해보도록 하자
<img src="https://velog.velcdn.com/images/emong_96/post/ca0fa683-fefc-4c23-b3d5-b2e971a2a36b/image.png" alt=""></p>
<p>SMS설정을 마쳐준후 확인버튼을 눌러준다</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/b9af2d98-69f6-4d79-9733-f5c045b7cfa5/image.png" alt=""></p>
<p>문자가 잘발송되는것을 알수가 있다!!</p>
<h2 id="server-생성">Server 생성</h2>
<p>NCP에서 API를 호출하기 위해서 header에 인증을 해줘야한다.
네이버 API 호출 가이드를 따라서 작성을 해보자!!</p>
<p>네이버 인증헤더 구성
<img src="https://velog.velcdn.com/images/emong_96/post/be555d63-9841-4051-819c-2deb8238f49c/image.png" alt=""></p>
<p>네이버 API 가이드 예제코드
<img src="https://velog.velcdn.com/images/emong_96/post/8b14287f-020f-47e2-af69-9e8344ba25f2/image.png" alt=""></p>
<p>네이버에 있는 예제코드를 활용하여 express서버를 생성하기로 하였다.</p>
<pre><code class="language-js">async function send_message(username,phone) {
  let user_phone_number = phone; //수신 전화번호 기입
  let user_name = username
  let resultCode = 404;
  const my_number = &#39;&#39;;
  const date = Date.now().toString();
  const options = {
    year: &#39;numeric&#39;,
    month: &#39;long&#39;,
    day: &#39;numeric&#39;,
    hour: &#39;numeric&#39;,
    minute: &#39;numeric&#39;,
    };
  let createdDate = new Date().toLocaleString(&#39;ko-KR&#39;, options);
  const uri = process.env.NCP_API_SERVICE_ID_KEY; //서비스 ID
  const secretKey = process.env.NCP_SECRET_KEY; // Secret Key
  const accessKey = process.env.NCP_API_ACCESS_KEY; //Access Key
  const method = &#39;POST&#39;;
  const space = &#39; &#39;;
  const newLine = &#39;\n&#39;;
  const url = `https://sens.apigw.ntruss.com/sms/v2/services/${uri}/messages`;
  const url2 = `/sms/v2/services/${uri}/messages`;


  const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secretKey);
  hmac.update(method);
  hmac.update(space);
  hmac.update(url2);
  hmac.update(newLine);
  hmac.update(date);
  hmac.update(newLine);
  hmac.update(accessKey);

  const hash = hmac.finalize();
  const signature = hash.toString(CryptoJS.enc.Base64);
  axios({
    method: method,
    // // request는 uri였지만 axios는 url이다
    url: url,
    headers: {
      &#39;Contenc-type&#39;: &#39;application/json; charset=utf-8&#39;,
      &#39;x-ncp-iam-access-key&#39;: accessKey,
      &#39;x-ncp-apigw-timestamp&#39;: date,
      &#39;x-ncp-apigw-signature-v2&#39;: signature,
    },
    // request는 body였지만 axios는 data다
    data: {
      type: &#39;SMS&#39;,
      countryCode: &#39;82&#39;,
      from: my_number,
      // 원하는 메세지 내용
      content: `${user_name}님 ${createdDate}출석 완료입니다.`,
      messages: [
        // 신청자의 전화번호
        { to: `${user_phone_number}` },
      ],
    },
  })
    .then((res) =&gt; {
      console.log(res.status);
    })
    .catch((err) =&gt; {
      console.log(err);
    });
  return resultCode;
}</code></pre>
<p>예제코드를 활용하여 기본틀을 작성하고 axios를 활용하여 네이버 API에 요청을 보내도록 작성하였다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/ec1746ab-8fd4-4806-8862-560e5a728665/image.png" alt=""></p>
<p>postman으로 잘 작동되는지 요청을 보내보았다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/0c852f69-5900-4e4a-a235-a94c69d5e558/image.png" alt=""></p>
<p>휴대폰으로도 메세지가 잘온다. 
localhost -&gt; 네이버 API -&gt; 휴대폰으로 문자 오는 형식으로 작동 된것이다!
다음번에는 EC2나 Lambda에 올려서 실행을 해볼예정이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[사이드 프로젝트] Daily_Study_Checker(1) - 프로젝트 시작, NCP
]]></title>
            <link>https://velog.io/@emong_96/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-DailyStudyChecker1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91-NCP</link>
            <guid>https://velog.io/@emong_96/%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-DailyStudyChecker1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91-NCP</guid>
            <pubDate>Tue, 03 Jan 2023 13:15:50 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-시작">프로젝트 시작</h2>
<p>호영이가 사이드 프로젝트를 하고 싶은데 할생각 있냐고 물어봐서 같이 하기로 하였다!
예전에 친구와 했던 프로젝트인데 스터디공부를 할때 카카오톡에 톡방을 만들어 아침 9시에 인증 샷을 올려서 공부를 했던 적이 있어서 위치 기반으로 출석체크를 할수 있는 앱을 만들고 싶다고 하였다.</p>
<p>스프링으로 기본적인 백엔드는 만들어져 있어서 나랑 둘이서 나는 프론트를 호영이는 백엔드를 담당하여 시작하기로 했다.
하지만 원래 같이 했었던 호영이 친구인 재원이라는 친구가 프론트를 담당하였는데 이번에 같이 다시 시작하기로해서 프론트(재원) 백엔드(호영, 경찬)으로 파트를 담담하고 시작하기로 하였다.</p>
<h3 id="구상">구상</h3>
<ol>
<li>Typescript (sms 서버, 프론트엔드 페이지)</li>
<li>ES6 이상 (sms 서버, 프론트엔드 페이지)</li>
<li>REST API (공통)</li>
<li>Springboot MVC (메인서버)</li>
<li>docker, docker compose (공통)</li>
<li>nginx (공통)</li>
<li>github action, jenkins 택1 (공통)</li>
<li>github, git workflow(공통)</li>
</ol>
<p>형식으로 프로젝트를 제작하기로 하였다. 
메인서버는 Springboot로 작성하고 내가 담당할 파트인 sms서버는 정해진 시간에만 출석체크를 하기 때문에 서버를 계속 켜놓을 필요가 없다고 판단하여
서버리스인 AWS Lambda로 제작하기로 하였다.</p>
<h3 id="sms-선택-이유">SMS 선택 이유</h3>
<p>사용자가 출석할 시간이나 출석을 위해 알림을 오게 만들어야하는데 카카오톡 알림톡과 문자 서비스를 고려해두고 선택하기로 하였다</p>
<h4 id="기존-카카오톡-연동의-문제점as-is">기존 카카오톡 연동의 문제점(AS-IS)</h4>
<ol>
<li>카카오 계정의 부재 (비즈니스 계정을 새로 사야됨)</li>
<li>배치서버를 따로 둬야 하고, 매니징할 콘솔 화면의 부재</li>
<li>JWT 를 통해서 로그인 하면 db에서 어떻게 매니징 할지 기술력 부족</li>
</ol>
<h4 id="ncloud-sms-api-서비스to-be">NCloud SMS API 서비스(TO-BE)</h4>
<ol>
<li>메세징 콘솔 화면이 존재</li>
<li>비교적 싼 가격</li>
<li>배치서버는 둬야 하지만, 메인 서버에 두면 됨</li>
<li>전화번호를 통해서 회원을 관리하면 되기 때문에 현재 db테이블과 맞음</li>
</ol>
<p>이라는 판단하에 네이버의 NCloud SMS API 서비스를 선택하게 되었다.</p>
<h2 id="ncpnaver-cloud-platform">NCP(Naver Cloud Platform)</h2>
<h3 id="naver-simple--easy-notification-service">Naver Simple &amp; Easy Notification Service</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/583fcf85-9c16-45d2-92e9-afbeef009b4e/image.png" alt=""></p>
<p>별도의 메세지 서버 구축없이 메시지 알림 기능을 구현할수 있는 서비스</p>
<ol>
<li>네이버 클라우드 플랫폼 접속하여 로그인 후 콘솔로 이동하여 서비스에서 Simple &amp; Easy Notification Service(이하 SENSE) 클릭</li>
</ol>
<p><img src="https://velog.velcdn.com/images/emong_96/post/56643e8b-3f83-4c9a-a58e-667d265e11de/image.png" alt=""></p>
<ol start="2">
<li>새 프로젝트 생성하기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/emong_96/post/ce05e641-21d7-427a-b283-9236d7d37caa/image.png" alt=""></p>
<ol start="3">
<li>프로젝트 생성 후 서비스ID 복사해두기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/emong_96/post/d93bfc89-d5d5-43db-9821-0a354040ef72/image.png" alt=""></p>
<ol start="4">
<li>발신 번호(Calling Number) 등록하기</li>
</ol>
<p><img src="https://velog.velcdn.com/images/emong_96/post/bd2ab8ff-a81e-4ba0-8a7f-889269efce28/image.png" alt=""></p>
<ol start="5">
<li>마이페이지-계정관리-인증키 관리에서 인증키 생성 후 복사해두기</li>
</ol>
<ul>
<li>API인증키? API를 호출한 사용자가 권한을 가진 사용자인지 식별하는 도구</li>
</ul>
<p><img src="https://velog.velcdn.com/images/emong_96/post/fae710da-b224-455c-8621-293fe7b645dd/image.png" alt=""></p>
<ol start="6">
<li>API 명세서 확인
메시지 발송 요청 URL
<img src="https://velog.velcdn.com/images/emong_96/post/0b417fda-6dd4-420a-834b-5810e164b1df/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/b44d3111-8a72-4c9d-8b30-046fc4bf5d46/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/eff99600-4013-4c3a-b9a3-b73e998d8824/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 마스터즈 - End Point 리팩토링(4)]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-End-Point-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%814</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-End-Point-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%814</guid>
            <pubDate>Wed, 14 Sep 2022 08:46:09 GMT</pubDate>
            <description><![CDATA[<h2 id="invoke-test">Invoke Test</h2>
<p>저번에 workflow의 Invoke까지 작성해봤기 때문에 이번에는 이 코드가 잘 작동하는지 테스트 코드를 작성해본다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/175bfc1a-8b65-44b6-ade2-01a179f07f60/image.png" alt=""></p>
<p>개인적으로 생각하며 테스트 코드를 짜보았는데 통과는 하였다 하지만 이테스트가 통과한다고 다된것인지, 아니면 어떤 값을 뱉어줘야 테스트가 통과하는지 잘모르기 때문에 이부분은 멘토님께 물어보기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/f3ccc102-dd54-4d1b-8e09-20c25c2554a2/image.png" alt="">
이때 까지 팀원들과 회의후 이슈를 한명이 몰아서 작성하였지만 Invoke부분은 내 담당이기 때문에 개인적으로 이슈를 올렸다.</p>
<p>그리고 오피스 아워때 여러가지를 알려주셨다.</p>
<ol>
<li>테스트코드중 <code>ReadAsAsync&lt;T&gt;</code>부분은 진짜 NHN Cloud에서 API를 요청하는 거기 때문에 테스트 할때 직접 불러오게 되면 비정상적인 접근으로 처리될수도 있으므로 불렀다 치고 테스트를 진행해야한다고 하셨다.</li>
<li>입력 값중에 headers와 requestUrl의 값이 필요한데 테스트용에서는 2가지의 방법이 있다고 하셨다. 직접 값을 불러와서 테스트를 하는것 OR 테스트용으로 직접 값을 넣어주는 방법이 있다고 하시고, 후자의 방법이 좋다고 하셨다.</li>
</ol>
<p>그렇게 테스트 코드를 멘토님 께서 도와주셨다!</p>
<pre><code class="language-cs">[DataTestMethod]
        [DataRow(HttpVerbs.POST, HttpStatusCode.OK, true, 200, &quot;hello world&quot;, &quot;lorem ipsum&quot;)]
        public async Task Given_Payload_When_Invoke_InvokeAsync_Then_It_Should_Return_Result(string method, HttpStatusCode statusCode, bool isSuccessful, int resultCode, string resultMessage, string body)
        {
            var model = new FakeResponseModel()
            {
                Header = new ResponseHeaderModel()
                {
                    IsSuccessful = isSuccessful,
                    ResultCode = resultCode,
                    ResultMessage = resultMessage
                },
                Body = body
            };
            var content = new ObjectContent&lt;FakeResponseModel&gt;(model, new JsonMediaTypeFormatter(), MediaTypeNames.Application.Json);
            var options = new HttpMessageOptions()
            {
                HttpResponseMessage = new HttpResponseMessage(statusCode) { Content = content }
            };

            var handler = new FakeHttpMessageHandler(options);

            var http = new HttpClient(handler);
            this._factory.Setup(p =&gt; p.CreateClient(It.IsAny&lt;string&gt;())).Returns(http);

            var workflow = new HttpTriggerWorkflow(this._factory.Object);

            var header = new RequestHeaderModel() { AppKey = &quot;hello&quot;, SecretKey = &quot;world&quot; };
            var headers = typeof(HttpTriggerWorkflow).GetField(&quot;_headers&quot;, BindingFlags.Instance | BindingFlags.NonPublic);
            headers.SetValue(workflow, header);

            var url = &quot;http://localhost:7071/api/HttpTrigger&quot;;
            var requestUrl = typeof(HttpTriggerWorkflow).GetField(&quot;_requestUrl&quot;, BindingFlags.Instance | BindingFlags.NonPublic);
            requestUrl.SetValue(workflow, url);

            var load = new FakeRequestModel()
            {
                FakeProperty1 = &quot;lorem ipsum&quot;
            };
            var payload = typeof(HttpTriggerWorkflow).GetField(&quot;_payload&quot;, BindingFlags.Instance | BindingFlags.NonPublic);
            payload.SetValue(workflow, load);

            var result = await workflow.InvokeAsync&lt;FakeResponseModel&gt;(new HttpMethod(method)).ConfigureAwait(false);

            result.Header.IsSuccessful.Should().Be(isSuccessful);
            result.Header.ResultCode.Should().Be(resultCode);
            result.Header.ResultMessage.Should().Be(resultMessage);
            result.Body.Should().Be(body);
        }</code></pre>
<p>직접 값을 넣지 않는 FakeModel을 작성하여 필요한 값들을 실제로 없는 값을 넣어주는 것이다!</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/482bf383-3ce6-474e-ae17-78aa915066f6/image.png" alt="">
테스트결과 잘 돌아가는것을 알수가 있다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/680ac21e-dd1f-4093-97f7-684e9f3bcb03/image.png" alt=""></p>
<p>workflow에 있는 함수도 수정하였다. 아래는 같고 위에 payload의 값이 없을때 request에 값을 넣어주는 것을 추가하였다! 팀원들이 다되면 테스트를 돌려보고 PR을 빨리 날려보고 싶다<del>!</del>!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 마스터즈 - End Point 리팩토링(3)]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-End-Point-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%813</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-End-Point-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%813</guid>
            <pubDate>Wed, 14 Sep 2022 06:50:15 GMT</pubDate>
            <description><![CDATA[<h2 id="invoke">Invoke</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/4329fdc3-1b0a-4da2-90f6-c8280ea99395/image.png" alt=""></p>
<p>리팩토링을 위해 Invoke부분을 뜯어보았다.
필요한 값들은
headers와 <code>_http</code> 그리고 requestUrl과 Get인지 Send인지 메서드 리스펀스의 종류가 필요하다.</p>
<p>Workflow파일에서 http를 정의를 해준다.
<img src="https://velog.velcdn.com/images/emong_96/post/80e9e1ae-787c-456a-bea9-0938186487ad/image.png" alt=""></p>
<p>headers는 멘토님이 생성해주실때 정의 되어있어서 그것을 사용하였다.</p>
<p>requestUrl도 의정님이 담당하여 위에서 정의가 되었다. 그 값들은 나는 가져와서 사용 하면 된다!!</p>
<p>하지만 어떤 종류의 파일인지 확인을 해야하기 떄문에 제너릭을 받은 값을 가지고 if 문을 사용하였다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/deca2bfa-b8a2-4f80-b8b8-d8dab6f70236/image.png" alt=""></p>
<p>값의 타입별로 그 파일에 맞는 값을 넣어줄려고 했다.</p>
<p>팀원에게 의논한결과 그러면 리팩토링의 의미가 없어지고 추상화가 안된다고 결론이나서 값을 어떻게 가지고와서 적어야할지 고민하였다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/eb812da3-37de-4d76-ba9f-7e040230eae8/image.png" alt="">
GetMessageResponse의 값을 왜 넣었을까 찾아보았다.
그결과 <code>ResponseModel&lt;ResponseItemBodyModel&lt;GetMessageResponseDate&gt;&gt;</code>
의 값을 의미 하고 있다는것을 알게 되었다. 
그렇게 제일 안쪽의 데이터값 그리고 바디값을 찾아보자.
<img src="https://velog.velcdn.com/images/emong_96/post/256c0a73-9847-4239-9d5d-594ecb7cce36/image.png" alt=""></p>
<p>Body의 값과 Data값이 이런 형식으로 나온다는것을 알게되었다~!</p>
<p>Get인지 Send인지 판별하는데 바디나 데이터 값이 필요하지 않기 때문에 
<img src="https://velog.velcdn.com/images/emong_96/post/5160ce92-8caf-4a3e-89e8-65b17c097188/image.png" alt="">
ResponseModel의 값으로 판별해준다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/3a1d4907-f2e2-4390-b7e8-30504933bafc/image.png" alt="">
T값을 받아서 코드를 작성해준다.</p>
<p>이렇게 작성하여 테스트 코드를 작성해 테스트가 통과하는지 확인해보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 마스터즈 - End Point 리팩토링(2)]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-End-Point-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%812</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-End-Point-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%812</guid>
            <pubDate>Wed, 14 Sep 2022 05:57:56 GMT</pubDate>
            <description><![CDATA[<h2 id="리팩토링-파트-담당">리팩토링 파트 담당</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/1d4fbee6-bc1f-4830-81b5-730d95e94a48/image.png" alt=""></p>
<p>우리는 총 3부분으로 나눠서 리팩토링을 담당하기로 했다.</p>
<p>header 부분은 멘토님께서 도와주셔서 완성하였고</p>
<p>query 부분은 준희님</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/fbc540d4-5044-4cd7-aab9-d34f649066ae/image.png" alt=""></p>
<p>build request url 부분은 의정님 
<img src="https://velog.velcdn.com/images/emong_96/post/cefd1f09-0c91-4968-ae9c-8f461ec866bc/image.png" alt=""></p>
<p>Invoke 부분은 내가 담당하기로 하였다.
<img src="https://velog.velcdn.com/images/emong_96/post/7a76db08-fb7d-4ad4-8413-f576059e338e/image.png" alt="">
Invoke 부분은 회의 결과 앞에서 나온값만 가져와서 넣어주면 되는거라 리팩토링이 필요없다 생각하였는데 멘토님께 여쭤본 결과 Invoke부분도 리팩토링이 가능하다는 말을 듣고 시도를 하게 되었다.</p>
<h2 id="interface-생성">Interface 생성</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8f1c82ea-e254-45c6-9ac7-4e8904e65f47/image.png" alt=""></p>
<p>sms 폴더안에 workflows폴더를 생성한후 아래에 HttpTriggerWorkflow.cs를 생성한다.
그 후 파일안에 워크플로우를 작성합니다.
<img src="https://velog.velcdn.com/images/emong_96/post/58180a04-2cba-42b8-9bd7-e553b999ec84/image.png" alt=""></p>
<p>그후 Test 폴더로 가서 똑같이 workfoles폴더 안에 HttpTriggerWorkflowTest.cs를 생성하여 해당 test코드를 작성합니다.</p>
<pre><code class="language-cs">[TestMethod]
        public void Given_NullHeader_When_Invoke_ValidateHeaderAsync_Then_It_Should_Throw_Exception()
        {
            var req = new Mock&lt;HttpRequest&gt;();
            var workflow = new HttpTriggerWorkflow(this._factory.Object);

            Func&lt;Task&gt; func = async () =&gt; await workflow.ValidateHeaderAsync(req.Object);

            func.Should().ThrowAsync&lt;RequestHeaderNotValidException&gt;();
        }</code></pre>
<p>테스트를 실행하게 되면
<img src="https://velog.velcdn.com/images/emong_96/post/c8d3f810-6ca5-4291-b874-fc3c1ba8e81b/image.png" alt="">
통과 되었다고 나온다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 76~80일차 스탠바잇 프로젝트 3주차]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-7680%EC%9D%BC%EC%B0%A8-%EC%8A%A4%ED%83%A0%EB%B0%94%EC%9E%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-7680%EC%9D%BC%EC%B0%A8-%EC%8A%A4%ED%83%A0%EB%B0%94%EC%9E%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 18 Aug 2022 12:53:46 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-7680일차">수업 76~80일차</h2>
<h3 id="프로젝트-3주차이고-엘리스-sw트랙의-마지막-주가-되었다-4월부터-시작하여-뭐를-듣고-뭐를-만들고-했지만-머리에는-깊게-들어오지-않고-아쉬움도-많이-남는-기간이였다-하지만-개발자를-하기로-마음먹고-frontend가-맞을지-backend가-맞을지도-모르는-상태였는데-엘리스를-통해-나는-백엔드쪽에-흥미가-많구나-api를-열어줄때-짜릿함이-느껴져서-방향을-잡을수-있어서-좋았던것-같습니다">프로젝트 3주차이고 엘리스 SW트랙의 마지막 주가 되었다. 4월부터 시작하여 뭐를 듣고 뭐를 만들고 했지만 머리에는 깊게 들어오지 않고 아쉬움도 많이 남는 기간이였다. 하지만 개발자를 하기로 마음먹고 FrontEnd가 맞을지 BackEnd가 맞을지도 모르는 상태였는데 엘리스를 통해 나는 백엔드쪽에 흥미가 많구나 API를 열어줄때 짜릿함이 느껴져서 방향을 잡을수 있어서 좋았던것 같습니다~!</h3>
<h2 id="이론">이론</h2>
<p>이번주는 너무 아쉬움이 많이 남는 주였다. 
큰 이유는 백엔드 파트를 담당했지만 프론트엔드 팀원들이 진전 상황이 너무 적고 만든 기능도 다 못쓰겠다는 말만 하는 분들이 계서서 원하는 만큼 완성을 못했다... 
1차때도 잘하지 못한거 같아서 잘만들어보고 싶었는데 아쉬운게 되어버렸다 ㅠㅠ
<img src="https://velog.velcdn.com/images/emong_96/post/268bda90-cdb4-4b03-b492-1d56806d17dc/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/ee0b8580-8046-4237-b3c6-2d99474cbcb3/image.png" alt=""></p>
<p>휴...</p>
<p>백엔드 파트에서는 더 기능을 개발하고 싶어도 이때까지 만들어준 기능도 다 못쓰는 상황이기 때문에 프론트 쪽에 붙어서 도와줬다. 
데이터 값을 어떻게 쓰는지 부터 데이터 값을 쓰기 어렵다는 스키마가 있어서 아예 스키마를 갈아버리고 당장 쓰기 쉽게 만들어 줬다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/525bb2cb-a0e2-4efa-9045-7c93ba1cedbb/image.png" alt="">
메인 홈페이지</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/57ad7ffb-0f89-4eeb-879a-84ce979902f6/image.png" alt=""></p>
<p>가게 리스트 정보</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8f44d334-cb57-424e-9f1c-7251523f0d1b/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/6852709d-edd0-4e52-93c6-cd6041e7353c/image.png" alt=""></p>
<p>가게 상세 페이지</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/76514a32-c504-45a9-a763-2b02947302fc/image.png" alt="">
예약 시간 설정</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/737bfc07-ffa9-42a6-813d-60d278b11ee9/image.png" alt="">
예약 완료</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/5854276f-b42d-48d4-b42e-5b3f3df53a84/image.png" alt="">
마이 페이지</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/01d39fdd-6360-45c9-8633-557629ca5725/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/beae53b6-80d9-42ca-af46-9f518555ad2f/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/e353874f-f30e-4a75-b8ef-cd5aac43c7e2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/0e62251b-dc99-4795-9dd1-ba5e543e7608/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/74c95972-76aa-4b4c-a124-331b02b0ec57/image.png" alt="">
<img src="https://velog.velcdn.com/images/emong_96/post/b119b3e1-28b9-4b43-9f02-f1b96a71f41f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/d87f443a-bb83-49db-b3e1-0317a6d1de32/image.png" alt=""></p>
<p>리드미</p>
<p>네.. 리드미도 제가 작성했습니다...</p>
<pre><code>![header](https://capsule-render.vercel.app/api?&amp;type=waving&amp;color=gradient&amp;height=300&amp;section=header&amp;text=Stand_By_Eat&amp;descSize=35&amp;descAlign=67&amp;fontSize=90&amp;fontAlign=50&amp;fontAlignY=45&amp;fontColor=fff&amp;animation=twinkling)

&lt;img src=&quot;https://standbyeat.s3.ap-northeast-2.amazonaws.com/store/1658990871007_%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A7%81+%EC%95%84%EC%9D%B4%EC%BD%98.png%7D&quot; width=&quot;300&quot;&gt;

# 스탠바잇🍽

&gt; 웨이팅 하기 힘드셨다구요? &lt;br /&gt;
&gt; 찾아본 맛집을 예약하고 싶으시다구요? &lt;br /&gt;
&gt; 점주분들이던 손님이던 음식 예약은 &lt;b&gt;스탠바잇&lt;b&gt;에게 맡겨주세요!&lt;br /&gt;

## 서비스 소개

웹을 이용하여 간편하게 식당 예약을 도와주는 서비스입니다.
점주분께서는 가게 등록을 하시어 손쉽게 예약 대행!
손님께서는 편하게 예약!

&lt;br /&gt;

## 1. 기획 의도, 목적

- 최근 골목식당 컨셉의 식당이 주를 이루게 되면서 갈만한 식당들은 대기가 1시간씩 걸리는 경우가 대부분입니다. 이런 소비자들의 불편함을 조금이나마 해소하기 위해 식당 시간 예약 서비스를 만들게 되었습니다.
- 해당 서비스를 이용하면 소비자들이 덥거나 추운 날씨에 밖에서 굳이 오랜시간 기다리거나 하지 않고 가서 바로 식당을 이용할 수 있을 것이라 기대합니다.

## 2. 웹 서비스의 주제 및, 최종적인 메인 기능과 서브 기능 설명

- 주제: 식당 예약 서비스
- 메인 기능
  - owner는 본인의 식당을 등록하고 위치, 메뉴 등을 입력한다.
  - user가 예약하고 싶은 날짜와 시간, 인원, 지역을 정해 예약가능한 식당을 보여준다.
  - user는 예약한 식당의 지도를 확인하고 카카오톡으로 예약완료 알림을 받는다.(사업자 등록증 및 금액 이슈로 카카오 알림톡 사용제한)
- 서브 기능
  - 로그인/회원가입
  - 맛집 목록
  - 마이페이지
  - 관리자 페이지
  - 지도 기능
  - 필터 기능
  - 메뉴 등록
  - 예약 관리
    &lt;br /&gt;

## 3. Figma

&lt;br /&gt;
[Figma Link](https://www.figma.com/file/ey4pOq90z2jHRkivXGDeLq/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A7%81-%EC%98%88%EC%95%BD)

## 4. 스키마 및 기능 명세서

&lt;br /&gt;
&lt;img src=&quot;https://standbyeat.s3.ap-northeast-2.amazonaws.com/store/1659005741832_%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B5%E1%84%86%E1%85%A1.png%7D&quot; width=&quot;700&quot;&gt;
&lt;img src=&quot;https://standbyeat.s3.ap-northeast-2.amazonaws.com/store/1659005741822_%E1%84%80%E1%85%B5%E1%84%82%E1%85%B3%E1%86%BC%E1%84%86%E1%85%AE%E1%86%AB%E1%84%89%E1%85%A5.png%7D&quot; width=&quot;700&quot;&gt;
&lt;br /&gt;
[Schema Link](https://docs.google.com/spreadsheets/d/18wSjjrUqZakAKz6wTBfXr6sPJwrnJSJTrbowwTkci1I/edit#gid=276855386)

## 5. API 명세서

&lt;br /&gt;
&lt;img src=&quot;https://standbyeat.s3.ap-northeast-2.amazonaws.com/store/1659005741848_%E1%84%91%E1%85%A9%E1%84%89%E1%85%B3%E1%84%90%E1%85%B3%E1%84%86%E1%85%A2%E1%86%AB.png%7D&quot; width=&quot;700&quot;&gt;
&lt;br /&gt;
[Postman Link](https://documenter.getpostman.com/view/20983410/UzQyrivc)

## 6. 스택

&lt;br /&gt;

### FRONT

&lt;img src=&quot;https://img.shields.io/badge/React-61DAFB?style=for-the-badge&amp;logo=React&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/CSS-1572B6?style=for-the-badge&amp;logo=CSS&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&amp;logo=HTML5&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/styled components-DB7093?style=flat-square&amp;logo=styled-components&amp;logoColor=white&quot;&gt;

### BACK

&lt;img src=&quot;https://img.shields.io/badge/Node.js-339933?style=for-the-badge&amp;logo=Node.js&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&amp;logo=JavaScript&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/Express-000000?style=for-the-badge&amp;logo=Express&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/MongoDB-47A248?style=for-the-badge&amp;logo=MongoDB&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/NGINX-009639?style=for-the-badge&amp;logo=NGINX&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/PM2-2B037A?style=for-the-badge&amp;logo=PM2&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/AmazonS3-569A31?style=for-the-badge&amp;logo=AmazonS3&amp;logoColor=white&quot;&gt;
&lt;img src=&quot;https://img.shields.io/badge/npm-CB3837?style=for-the-badge&amp;logo=npm&amp;logoColor=white&quot;&gt;

## 7. 구성원 역할

| 이름   | 역할                                                                          | 구현 기능                                                                     |
| ------ | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| 김다영 | &lt;img src=&quot;https://img.shields.io/badge/FE-FECC00?style=flat-square&quot;/&gt;- 팀장👍 | 로고, 아이콘 디자인 및 홈 화면, 식당 리스트 구현, 식당 예약정보 등록          |
| 정민희 | &lt;img src=&quot;https://img.shields.io/badge/FE-FECC00?style=flat-square&quot;/&gt;         | 로그인 관련 기능, 마이페이지 유저 관련 프로필, 지역별 필터기능, 카카오맵 사용 |
| 진형빈 | &lt;img src=&quot;https://img.shields.io/badge/FE-FECC00?style=flat-square&quot;/&gt;         |                                                                               |
| 오경찬 | &lt;img src=&quot;https://img.shields.io/badge/BE-00A1E9?style=flat-square&quot;/&gt;         | 가게 등록, 예약 시스템, 메뉴 등록, 예약 시간, 이미지 등록, AWS S3 등 기능     |
| 박태훈 | &lt;img src=&quot;https://img.shields.io/badge/BE-00A1E9?style=flat-square&quot;/&gt;         | 사용자,가게 사장,관리자 관련 기능                                             |
</code></pre><p>이번 프로젝트 각종 링크들
API 명세서 : <a href="https://documenter.getpostman.com/view/20983410/UzQyrivc">https://documenter.getpostman.com/view/20983410/UzQyrivc</a>
피그마 : <a href="https://www.figma.com/file/ey4pOq90z2jHRkivXGDeLq/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A7%81-%EC%98%88%EC%95%BD?node-id=0%3A1">https://www.figma.com/file/ey4pOq90z2jHRkivXGDeLq/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A7%81-%EC%98%88%EC%95%BD?node-id=0%3A1</a>
스키마 : <a href="https://docs.google.com/spreadsheets/d/18wSjjrUqZakAKz6wTBfXr6sPJwrnJSJTrbowwTkci1I/edit#gid=276855386">https://docs.google.com/spreadsheets/d/18wSjjrUqZakAKz6wTBfXr6sPJwrnJSJTrbowwTkci1I/edit#gid=276855386</a></p>
<p>좋지못했던 좋았던 많은 것을 배웠던 3주가 되었던거 같다. 
앞으로는 백엔드 쪽을 더 많이 공부하여 멋진 개발자가 되었으면 좋겠다<del>!</del>!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 71~75일차 스탠바잇 프로젝트 2주차]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-7175%EC%9D%BC%EC%B0%A8-%EC%8A%A4%ED%83%A0%EB%B0%94%EC%9E%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-7175%EC%9D%BC%EC%B0%A8-%EC%8A%A4%ED%83%A0%EB%B0%94%EC%9E%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 18 Aug 2022 11:43:51 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-7175일차">수업 71~75일차</h2>
<h3 id="프로젝트를-시작한지-일주일이-지나고-이주차에-들어가게-되었다-백엔드-파트에서는-많은걸-해봤기-때문에-어떻게-해야할지-정리를-해서-차근차근-해나가게-되었다">프로젝트를 시작한지 일주일이 지나고 이주차에 들어가게 되었다. 백엔드 파트에서는 많은걸 해봤기 때문에 어떻게 해야할지 정리를 해서 차근차근 해나가게 되었다.</h3>
<h2 id="내용">내용</h2>
<p>나는 1차 프로젝트때 유저정보를 담담했었기 때문에 이번에는 유저정보를 백엔드 다른 팀원에게 넘겨주고 나는 가게정보와 예약정보를 담당하기로 했다.</p>
<p>가게 정보와 예약정보의 스키마를 만들고
<img src="https://velog.velcdn.com/images/emong_96/post/72f61c33-140e-469c-96fa-387a4ca32418/image.png" alt=""></p>
<p>DB에 저장 까지 하였다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/43a80d25-c4e5-4229-8328-c9ff8e0459a9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/59e23524-b3b1-44bf-a799-7fb9c325d4dc/image.png" alt="">
Postman으로 가게 정보를 가져올때 등록된 메뉴까지 가져올수 있게 Populate를 설정해주어 가게 정보안에 메뉴 스키마 정보까지 나올수있게 설정하였다. 배열로 저장이 되어 여러개를 넣을수 있다.
(이부분에서 어떻게 배열로 넣을까 고민을 하여 시간을 많이 보냈다....)</p>
<p>이번 프로젝트에서는 AWS를 사용하여 이미지를 저장하기로 마음먹었기 때문에 
AWS S3사용에 도전을 하였다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/2d9455f0-17aa-4b91-8ed2-127f852c315e/image.png" alt=""></p>
<p>AWS는 처음이라 계정 설정 해주는데만 하루가 지나가 버렸다. (심지어 실패해서 코치님에게 SOS요청했음...)</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/d14eb368-a530-42b3-96d9-5f6eb724babc/image.png" alt="">
URL을 보게되면 s3에서 불러온 링크인것을 알수가 있다..!!
이렇게 AWS S3에 이미지를 저장하게 설정까지 마쳤다<del>!</del>!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 마스터즈 - End Point 리팩토링(1)]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 18 Aug 2022 08:56:30 GMT</pubDate>
            <description><![CDATA[<h2 id="마스터즈-합류">마스터즈 합류</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/c0c183a3-685f-41a4-b1ea-bd700c7cd5d4/image.png" alt=""></p>
<p>챌린저스를 끝내고 마스터즈 인원 발표를 기다리고 있는데 멘토님께서 리드맨티 제안을 하셨다!</p>
<p>하지만 다른것도 바쁘고 많을 시간을 투자하지 못하지 때문에 죄송하다고 일반 팀원으로 하고 싶다고 말씀드렸다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/d41d2d99-0082-4668-a031-bfb169f67c3f/image.png" alt=""></p>
<p>그결과 4번 번호로 합류하게 되었다<del>!</del>!
(분명 15명이였는데..?)</p>
<h2 id="엔드포인트-리팩토링">엔드포인트 리팩토링</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/394a7072-7edd-4a85-9053-0f78a29a09ee/image.png" alt=""></p>
<p>3개의 팀으로 나뉘어서 우리팀은 엔드포인트 리펙토링을 담당하게되었다.</p>
<p>일단 이번주는 nt-sms Trigger 폴더의 코드들을 어떻게 리팩토링 할것인지에 대한 워크플로우를 생각해오기로 했다.</p>
<h3 id="trigger">Trigger</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/cbc41bd6-5728-4e52-be8e-b9b4405aab72/image.png" alt=""></p>
<ul>
<li>GetMessage : 발송 단일 검색</li>
<li>ListMessages : 발송 목록 검색</li>
<li>ListMessageStatus : 검색 api</li>
<li>ListSenders : 발신번호 목록 검색</li>
<li>SendMessages : 문자메세지 보내기</li>
</ul>
<pre><code class="language-cs">//GetMessage.cs
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

using Aliencube.AzureFunctions.Extensions.Common;

using FluentValidation;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;

using Toast.Common.Builders;
using Toast.Common.Configurations;
using Toast.Common.Extensions;
using Toast.Common.Models;
using Toast.Common.Validators;
using Toast.Sms.Configurations;
using Toast.Sms.Models;
using Toast.Sms.Validators;

namespace Toast.Sms.Triggers
{
    public class GetMessage
    {
        private readonly ToastSettings&lt;SmsEndpointSettings&gt; _settings;
        private readonly IValidator&lt;GetMessageRequestQueries&gt; _validator;
        private readonly HttpClient _http;
        private readonly ILogger&lt;GetMessage&gt; _logger;

        public GetMessage(ToastSettings&lt;SmsEndpointSettings&gt; settings, IValidator&lt;GetMessageRequestQueries&gt; validator, IHttpClientFactory factory, ILogger&lt;GetMessage&gt; log)
        {
            this._settings = settings.ThrowIfNullOrDefault();
            this._validator = validator.ThrowIfNullOrDefault();
            this._http = factory.ThrowIfNullOrDefault().CreateClient(&quot;messages&quot;);
            this._logger = log.ThrowIfNullOrDefault();
        }

        [FunctionName(nameof(GetMessage))]
        //OpenApiParameter 모음
        [OpenApiOperation(operationId: &quot;Messages.Get&quot;, tags: new[] { &quot;messages&quot; })]
        [OpenApiSecurity(&quot;app_auth&quot;, SecuritySchemeType.Http, Scheme = OpenApiSecuritySchemeType.Basic, Description = &quot;Toast API basic auth&quot;)]
        // [OpenApiSecurity(&quot;app_key&quot;, SecuritySchemeType.ApiKey, Name = &quot;x-app-key&quot;, In = OpenApiSecurityLocationType.Header, Description = &quot;Toast app key&quot;)]
        // [OpenApiSecurity(&quot;secret_key&quot;, SecuritySchemeType.ApiKey, Name = &quot;x-secret-key&quot;, In = OpenApiSecurityLocationType.Header, Description = &quot;Toast secret key&quot;)]
        // [OpenApiSecurity(&quot;function_key&quot;, SecuritySchemeType.ApiKey, Name = &quot;x-functions-key&quot;, In = OpenApiSecurityLocationType.Header, Description = &quot;Functions API key&quot;)]
        [OpenApiParameter(name: &quot;requestId&quot;, Type = typeof(string), In = ParameterLocation.Path, Required = true, Description = &quot;SMS request ID&quot;)]
        [OpenApiParameter(name: &quot;recipientSeq&quot;, Type = typeof(int), In = ParameterLocation.Query, Required = true, Description = &quot;SMS request sequence number&quot;)]
        [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: &quot;application/json&quot;, bodyType: typeof(GetMessageResponse), Example = typeof(GetMessageResponseModelExample), Description = &quot;The OK response&quot;)]
        [OpenApiResponseWithoutBody(statusCode: HttpStatusCode.BadRequest, Description = &quot;The input was invalid&quot;)]
        [OpenApiResponseWithoutBody(statusCode: HttpStatusCode.InternalServerError, Description = &quot;The service has got an unexpected error&quot;)]
        public async Task&lt;IActionResult&gt; Run(
            [HttpTrigger(AuthorizationLevel.Function, &quot;GET&quot;, Route = &quot;messages/{requestId:regex(^\\d+\\w+$)}&quot;)] HttpRequest req,
            string requestId)
        {
            _logger.LogInformation(&quot;C# HTTP trigger function processed a request.&quot;);

            var headers = default(RequestHeaderModel);
            try
            {
                headers = req.To&lt;RequestHeaderModel&gt;(useBasicAuthHeader: true).Validate();
                // headers = await req.To&lt;RequestHeaderModel&gt;(SourceFrom.Header).Validate().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                return new BadRequestResult();
            }

            var queries = default(GetMessageRequestQueries);
            try 
            {
                queries = await req.To&lt;GetMessageRequestQueries&gt;(SourceFrom.Query).Validate(this._validator).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                return new BadRequestResult();
            }

            var paths = new GetMessageRequestPaths() { RequestId = requestId };
// query parameter추가
            var requestUrl = new RequestUrlBuilder()
                .WithSettings(this._settings, this._settings.Endpoints.GetMessage)
                .WithHeaders(headers)
                .WithQueries(queries)
                .WithPaths(paths).Build();
// 세팅값, 해더, 쿼리값, 패치 넣고 빌드
            this._http.DefaultRequestHeaders.Add(&quot;X-Secret-Key&quot;, headers.SecretKey);
            var result = await this._http.GetAsync(requestUrl).ConfigureAwait(false);
// 시크릿값 넣어줌
            var payload = await result.Content.ReadAsAsync&lt;GetMessageResponse&gt;().ConfigureAwait(false);

            return new OkObjectResult(payload);
        }
    }
}</code></pre>
<p>저번주에 사용해봤던 API의 흐름이다. 사용한것을 직접 코드로 보니 어느정도는 흐름을 알게된거 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 4주차]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-4%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-4%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Thu, 18 Aug 2022 06:04:25 GMT</pubDate>
            <description><![CDATA[<h2 id="nhn-cloud-sms-api">NHN Cloud SMS API</h2>
<p>오늘은 NHN Cloud의 API의 대해서 알아보고 사용해볼 예정이다.</p>
<h3 id="nhn-cloude-가입">NHN Cloude 가입</h3>
<p>해당 홈페이지 Console에서 SMS API를 사용할수 있다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/35940664-0060-42ec-976c-0183a1340fbc/image.png" alt=""></p>
<p>REST API를 사용하기위해서 baseURL과 Appkey 그리고 SecretKey를 사용한다. 이것은 개인의 값이기때문에 노출되서는 안된다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/d9930232-9a07-45ff-8fd6-4391c77a2785/image.png" alt=""></p>
<h3 id="postman">Postman</h3>
<p>Open Api이기 때문에 바로 Postman으로 확인해볼수있다.
나는 다른 프로젝트를 진행할때 API 명세서도 Postman으로 만들어봤기 때문에 익숙해서 좋았다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/09906d75-c32d-4b02-947c-3f6fe3797154/image.png" alt=""></p>
<p>Postman을 세팅하고 </p>
<ul>
<li>문자메세지 보내기</li>
<li>발송 단일 검색</li>
<li>발송 목록 검색</li>
<li>발신번호 목록 검색</li>
</ul>
<p>라는 각각의 리스트를 만들었다.</p>
<h3 id="문자메세지-보내기">문자메세지 보내기</h3>
<p>baseUrl, appKey, SecertKey를 입력후 
<img src="https://velog.velcdn.com/images/emong_96/post/3439b200-404d-4d88-a0e8-37dc18926c51/image.png" alt="">
형식에 맞는 Request body를 입력해준다,</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/336fdf4a-5aa0-4e3c-b8c8-bb94d313021d/image.png" alt=""></p>
<p>내가 원하는 값을 넣게되면 전송이 완료 되었다고 뜬다!</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/0a73c231-9a36-4096-a9e4-d33e8f55e0c6/image.png" alt=""></p>
<p>문자도 잘 도착한다!</p>
<h3 id="발송-단일-검색">발송 단일 검색</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/242d89c6-1547-4c58-953a-1325401248e8/image.png" alt=""></p>
<h3 id="발송-목록-검색">발송 목록 검색</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/c9c14572-f33a-40f7-92a5-999659584b3c/image.png" alt=""></p>
<h3 id="발신번호-목록-검색">발신번호 목록 검색</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/f8d68614-bfe1-44c0-b898-c587f0420140/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 3주차 과제]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C</guid>
            <pubDate>Sat, 30 Jul 2022 13:24:41 GMT</pubDate>
            <description><![CDATA[<h1 id="step-4">Step 4</h1>
<h2 id="events--push-pull_request">Events – push, pull_request</h2>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions&#39;

on:
  push:
    branches:
      - master
    tags:
      - &#39;v*&#39;
  pull_request:
    branches:
      - master

jobs:
  build:
    name: Build Apps

    runs-on: ubuntu-latest

    steps:
      - name: Checkout the repo
        uses: actions/checkout@v2

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: &#39;6.x&#39;

      - name: Restore NuGet packages
        shell: bash
        run: |
          dotnet restore ./api

      - name: Build solution
        shell: bash
        run: |
          dotnet build ./api -c Release

      - name: Create FunctionApp artifact
        shell: bash
        run: |
          dotnet publish ./api -c Release -o ./api/bin/published

      - name: Upload FunctionApp artifact
        uses: actions/upload-artifact@v2
        with:
          name: apiapp
          path: api/bin/published

  release:
    name: Release Apps
    needs: build

    runs-on: ubuntu-latest

    steps:
      - name: Download FunctionApp artifact
        uses: actions/download-artifact@v2
        with:
          name: apiapp
          path: artifacts/api

      - name: Zip FunctionApp artifact
        shell: bash
        run: |
          pushd artifacts/api
          zip -qq -r apiapp.zip .
          popd

          mv artifacts/api/apiapp.zip artifacts/apiapp.zip

      - name: Release FunctionApp artifact to GitHub
        uses: &#39;marvinpinto/action-automatic-releases@latest&#39;
        with:
          automatic_release_tag: &#39;latest&#39;
          repo_token: &#39;${{ secrets.GITHUB_TOKEN }}&#39;
          prerelease: false
          files: |
            artifacts/apiapp.zip
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/0ea1bbc3-2d35-4f48-9bdd-339dfcfe3fac/image.png" alt=""></p>
<h2 id="events--workflow_dispatch">Events – workflow_dispatch</h2>
<pre><code class="language-yaml">name: &#39;Manual Release&#39;

on:
  workflow_dispatch:
    inputs:
      title:
        type: string
        required: true
        description: Enter the release title

jobs:
  build:
    name: Build Apps

    runs-on: ubuntu-latest

    steps:
      - name: Checkout the repo
        uses: actions/checkout@v2

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: &#39;6.x&#39;

      - name: Restore NuGet packages
        shell: bash
        run: |
          dotnet restore ./api

      - name: Build solution
        shell: bash
        run: |
          dotnet build ./api -c Release

      - name: Create FunctionApp artifact
        shell: bash
        run: |
          dotnet publish ./api -c Release -o ./api/bin/published

      - name: Upload FunctionApp artifact
        uses: actions/upload-artifact@v2
        with:
          name: apiapp
          path: api/bin/published

  release:
    name: Release Apps
    needs: build

    runs-on: ubuntu-latest

    steps:
      - name: Download FunctionApp artifact
        uses: actions/download-artifact@v2
        with:
          name: apiapp
          path: artifacts/api

      - name: Zip FunctionApp artifact
        shell: bash
        run: |
          pushd artifacts/api
          zip -qq -r apiapp.zip .
          popd

          mv artifacts/api/apiapp.zip artifacts/apiapp.zip

      - name: Release FunctionApp artifact to GitHub
        uses: &#39;marvinpinto/action-automatic-releases@latest&#39;
        with:
          automatic_release_tag: &#39;latest&#39;
          repo_token: &#39;${{ secrets.GITHUB_TOKEN }}&#39;
          prerelease: false
          title: ${{ github.event.inputs.title }}
          files: |
            artifacts/apiapp.zip
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/4abb0fda-b9bf-4cfe-977a-61ad02b2ae43/image.png" alt=""></p>
<h2 id="events--workflow_call">Events – workflow_call</h2>
<h3 id="mainyaml">main.yaml</h3>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions&#39;

on:
  push:
    branches:
      - master
    tags:
      - &#39;v*&#39;
  pull_request:
    branches:
      - master

jobs:
  call_build:
    uses: ./.github/workflows/ci.yaml
    with:
      artifact_name: apiapp

  call_release:
    uses: ./.github/workflows/cd.yaml
    needs: call_build
    with:
      title: latest
      artifact_name: apiapp
    secrets: inherit
</code></pre>
<h3 id="manual-releaseyaml">manual-release.yaml</h3>
<pre><code class="language-yaml">name: &#39;Manual Release&#39;

on:
  workflow_dispatch:
    inputs:
      title:
        type: string
        required: true
        description: Enter the release title

jobs:
  call_build:
    uses: ./.github/workflows/ci.yaml
    with:
      artifact_name: apiapp

  call_release:
    uses: ./.github/workflows/cd.yaml
    needs: call_build
    with:
      title: ${{ github.event.inputs.title }}
      artifact_name: apiapp
      automatic_release_tag: &#39;latest&#39;
    secrets: inherit
</code></pre>
<h3 id="ciyaml">ci.yaml</h3>
<pre><code class="language-yaml">name: &#39;Continuous Integration&#39;

on:
  workflow_call:
    inputs:
      artifact_name:
        type: string
        required: false
        description: Name of artifact
        default: apiapp

jobs:
  build:
    name: Build Apps

    runs-on: ubuntu-latest

    steps:
      - name: Checkout the repo
        uses: actions/checkout@v2

      - name: Setup .NET SDK
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: &#39;6.x&#39;

      - name: Restore NuGet packages
        shell: bash
        run: |
          dotnet restore ./api

      - name: Build solution
        shell: bash
        run: |
          dotnet build ./api -c Release

      - name: Create FunctionApp artifact
        shell: bash
        run: |
          dotnet publish ./api -c Release -o ./api/bin/published

      - name: Upload FunctionApp artifact
        uses: actions/upload-artifact@v2
        with:
          name: ${{ inputs.artifact_name }}
          path: api/bin/published
</code></pre>
<h3 id="cdyaml">cd.yaml</h3>
<pre><code class="language-yaml">name: &#39;Continuous Deployment&#39;

on:
  workflow_call:
    inputs:
      title:
        type: string
        required: true
        description: Enter the release title
      artifact_name:
        type: string
        required: false
        description: Name of artifact
        default: apiapp

jobs:
  release:
    name: Release Apps

    runs-on: ubuntu-latest

    steps:
      - name: Download FunctionApp artifact
        uses: actions/download-artifact@v2
        with:
          name: ${{ inputs.artifact_name }}
          path: artifacts/api

      - name: Zip FunctionApp artifact
        shell: bash
        run: |
          pushd artifacts/api
          zip -qq -r ${{ inputs.artifact_name }}.zip .
          popd

          mv artifacts/api/${{ inputs.artifact_name }}.zip artifacts/${{ inputs.artifact_name }}.zip

      - name: Release FunctionApp artifact to GitHub
        uses: &#39;marvinpinto/action-automatic-releases@latest&#39;
        with:
          repo_token: &#39;${{ secrets.GITHUB_TOKEN }}&#39;
          automatic_release_tag: &#39;latest&#39;
          prerelease: false
          title: ${{ inputs.title }}
          files: |
            artifacts/${{ inputs.artifact_name }}.zip
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/119c5f1c-31c1-493c-9a88-9e46f9fddb44/image.png" alt=""></p>
<h1 id="step-5">Step 5</h1>
<h2 id="multi-stage-deployments">Multi-Stage Deployments</h2>
<h3 id="mainyaml-1">main.yaml</h3>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions&#39;

on:
  push:
    branches:
      - master
    tags:
      - &#39;v*&#39;
  pull_request:
    branches:
      - master

jobs:
  call_build:
    uses: ./.github/workflows/ci.yaml
    with:
      artifact_name: apiapp

  call_release_dev:
    uses: ./.github/workflows/cd.yaml
    needs: call_build
    with:
      title: latest
      artifact_name: apiapp
      env: DEV
    secrets: inherit

  call_release_prod:
    uses: ./.github/workflows/cd.yaml
    needs: call_release_dev
    with:
      title: latest
      artifact_name: apiapp
      env: PROD
    secrets: inherit
</code></pre>
<h3 id="cdyaml-1">cd.yaml</h3>
<pre><code class="language-yaml">name: &#39;Continuous Deployment&#39;

on:
  workflow_call:
    inputs:
      title:
        type: string
        required: true
        description: Enter the release title
      artifact_name:
        type: string
        required: false
        description: Name of artifact
        default: apiapp
      env:
        type: string
        required: false
        description: Environment name
        default: DEV

jobs:
  release:
    name: Release Apps

    runs-on: ubuntu-latest

    environment: ${{ inputs.env }}

    steps:
      - name: Download FunctionApp artifact
        uses: actions/download-artifact@v2
        with:
          name: ${{ inputs.artifact_name }}
          path: artifacts/api

      - name: Zip FunctionApp artifact
        shell: bash
        run: |
          pushd artifacts/api
          zip -qq -r ${{ inputs.artifact_name }}-${{ inputs.env }}.zip .
          popd

          mv artifacts/api/${{ inputs.artifact_name }}-${{ inputs.env }}.zip artifacts/${{ inputs.artifact_name }}-${{ inputs.env }}.zip

      - name: Release FunctionApp artifact to GitHub
        uses: &#39;marvinpinto/action-automatic-releases@latest&#39;
        with:
          repo_token: &#39;${{ secrets.GITHUB_TOKEN }}&#39;
          automatic_release_tag: &#39;latest&#39;
          prerelease: false
          title: ${{ inputs.title }}
          files: |
            artifacts/${{ inputs.artifact_name }}-${{ inputs.env }}.zip
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/facbcddf-8ece-47c2-9739-5c7d9082b24c/image.png" alt=""></p>
<p><a href="https://github.com/dhrudcks01/GitHub-Action">https://github.com/dhrudcks01/GitHub-Action</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 3주차]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-3%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-3%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 30 Jul 2022 12:58:13 GMT</pubDate>
            <description><![CDATA[<h2 id="github-action">GitHub Action</h2>
<p>이번주는 GitHub Action에 대해서 진행을 하였다.
<img src="https://velog.velcdn.com/images/emong_96/post/cbb092b5-7872-47ee-8ad2-6c0847355a8e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/48b3086b-c0b0-4c6e-af10-fd39c5b57ddd/image.png" alt="">
두개를 잘깔아준후 github과 연동하여 하나를 새로 파준다.</p>
<p>연동시켜준 .github/workflows아래의 main.yml파일에 아래의 코드를 작성한다.</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  fist-job:
    name: &#39;first Job&#39;
    runs-on: &#39;ubuntu-latest&#39;

    steps:
      - name: &#39;Say Hello World on step 1&#39;
        shell: bash
        run: |
          echo &quot;Hello World from Step 1&quot;

      - name: &#39;Say Lorem Ipsum on step 2&#39;
        shell: pwsh
        run: |
          echo &quot;Lorem Ipsum from step 2&quot;
</code></pre>
<p>작성후 깃허브에 Push를 하고 Actions를 보면 Fisrt Job이 실행되어 있다!</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/52819119-24c4-42d5-b78b-90327c0f75b4/image.png" alt=""></p>
<p>실행이 잘되어 보인다!</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

env:
  WORKFLOW_LEVEL: &#39;This value comes fom the workflow level&#39;

jobs:
  fist-job:
    name: &#39;first Job&#39;
    runs-on: &#39;ubuntu-latest&#39;

    env:
      JOB_LEVEL_1: &#39;This value comes from the job 1&#39;
    steps:
      - name: &#39;Say Hello World from step 1&#39;
        shell: bash
        env:
          STEP_LEVEL_1: &#39;This value comes fom the step 1&#39;
        run: |
          echo &quot;Hello World from Step 1&quot;
          echo &quot;$WORKFLOW_LEVEL&quot;
          echo &quot;$JOB_LEVEL_1&quot;
          echo &quot;$JOB_LEVEL_2&quot;
          echo &quot;$STEP_LEVEL_1&quot;
          echo &quot;$STEP_LEVEL_2&quot;

      - name: &#39;Say Lorem Ipsum on step 2&#39;
        shell: pwsh
        env:
          STEP_LEVEL_2: &#39;This value comes from the step 2&#39;
        run: |
          echo &quot;Lorem Ipsum from step 2&quot;
          echo &quot;$env:WORKFLOW_LEVEL&quot;
          echo &quot;$env:JOB_LEVEL_1&quot;
          echo &quot;$env:JOB_LEVEL_2&quot;
          echo &quot;$env:STEP_LEVEL_1&quot;
          echo &quot;$env:STEP_LEVEL_2&quot;

  second-job:
    name: &#39;Second Job&#39;
    runs-on: &#39;ubuntu-latest&#39;
    env:
      JOB_LEVEL_2: &#39;This value comes from the job 2&#39;
    steps:
      - name: &#39;Say Hello World on step 1&#39;
        shell: bash
        env:
          STEP_LEVEL_1: &#39;This value comes from the step 1&#39;
        run: |
          echo &quot;Hello World from Step 1&quot;
          echo &quot;$WORKFLOW_LEVEL&quot;
          echo &quot;$JOB_LEVEL_1&quot;
          echo &quot;$JOB_LEVEL_2&quot;
          echo &quot;$STEP_LEVEL_1&quot;
          echo &quot;$STEP_LEVEL_2&quot;

      - name: &#39;Say Lorem Ipsum on step 2&#39;
        shell: pwsh
        env:
          STEP_LEVEL_2: &#39;This value comes from the step 2&#39;
        run: |
          echo &quot;Lorem Ipsum on step 2&quot;
          echo &quot;$env:WORKFLOW_LEVEL&quot;
          echo &quot;$env:JOB_LEVEL_1&quot;
          echo &quot;$env:JOB_LEVEL_2&quot;
          echo &quot;$env:STEP_LEVEL_1&quot;
          echo &quot;$env:STEP_LEVEL_2&quot;
</code></pre>
<p>으로 수정을 하여 푸쉬하게 되면</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/38f684ef-87b2-4bc6-a5a8-679276052c96/image.png" alt=""></p>
<p>first Job에서 step1에서는 second Job이 안보이며 step2도 안보이게 된다.</p>
<p>step2에서는 마찬가지로 step1이 안보이게 된다</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/292b205d-dc93-4d7f-84c7-a73d6c783271/image.png" alt=""></p>
<p>반대로 second Job에서는 first Job이 안보이게 된다 이렇게 환경변수의 위치가 중요하다!</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

env:
  WORKFLOW_LEVEL: &#39;This value comes fom the workflow level&#39;

jobs:
  fist-job:
    name: &#39;first Job&#39;
    runs-on: &#39;ubuntu-latest&#39;

    steps:
      - name: &#39;Set environment variable 1&#39;
        shell: bash
        run: |
          echo $GITHUB_ENV
          echo &quot;STEP_1=&#39;This value comes from the step 1&#39;&quot; &gt;&gt; $GITHUB_ENV

      - name: &#39;Set environment variable 2&#39;
        shell: pwsh
        run: |
          echo &quot;STEP_2=&#39;This value comes from the step 2&#39;&quot; `
          | Out-File -FilePath $env:GITHUB_ENV -Encoding utf-8 -Append

      - name: &#39;Get environment variables&#39;
        shell: bash
        run: |
          echo &quot;WORKFLOW: ${{ env.WORKFLOW_LEVEL }}&quot;
          echo &quot;STEP 1: ${{ env.STEP_1 }}&quot;
          echo &quot;STEP 2: ${{ env.STEP_2 }}&quot;
</code></pre>
<p>위의 코드대로 환경변수를 github에 저장할수 있다 아래의 사진을 보자
<img src="https://velog.velcdn.com/images/emong_96/post/c9418b1a-05eb-4383-9b85-e4421016f164/image.png" alt=""></p>
<p>power shell과 bash는 변수 가져오는 방식이 다름
github atcion은 ${{}} 형식으로 쓴다
위 사진과 같이 데이터 들이 전부 잘나오는것을 알수가 있다</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

env:
  WORKFLOW_LEVEL: &#39;This value comes fom the workflow level&#39;

jobs:
  fist-job:
    name: &#39;first Job&#39;
    runs-on: &#39;ubuntu-latest&#39;

    steps:
      - name: &#39;Set output value 1&#39;
        id: step1
        shell: bash
        run: |
          STEP_1=&#39;This value comes from the step 1&#39;

          echo &quot;::add-mask::$STEP_1&quot;
          echo &quot;::set-output name=value1::$STEP_1&quot;

      - name: &#39;Set output value 2&#39;
        id: step2
        shell: pwsh
        run: |
          $STEP_2=&#39;This value comes from the step 2&#39;

          echo &quot;::set-output name=value2::$STEP_2&quot;

      - name: &#39;Get output values&#39;
        shell: bash
        run: |
          echo &quot;STEP 1: ${{ steps.step1.outputs.value1 }}&quot;
          echo &quot;STEP 2: ${{ steps.step2.outputs.value2 }}&quot;
</code></pre>
<p>위코드를 사용하면 아래 사진과 같은 값이 나오는데
<img src="https://velog.velcdn.com/images/emong_96/post/dd86ae69-6375-4f4a-8062-dc1262650273/image.png" alt=""></p>
<p>output value 1의 값을 step1에 할당한다.
set-output이라는것이 다른 step에서 참조할수 있게 만들어준다,
add-mask를 적용하면 참조하는 값이 <code>***</code>으로 변환되어 나오게 된다</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8ea62ddd-fc8b-49f4-9198-04a8b82e70dd/image.png" alt=""></p>
<p>GitHub설정에서 Actions secrets를 설정해줄수 있는데 SECRET_1을 Hello World로 설정해주도록 합니다.
<img src="https://velog.velcdn.com/images/emong_96/post/be076763-a645-4926-8702-49273a2f66e7/image.png" alt=""></p>
<p>설정해주면 이렇게 생성되었다고 나오게 되는데</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/7eb6cd77-1437-46dc-bd1d-6d6eb7c41995/image.png" alt="">
이렇게 시크릿 값도 나오게 된다 시크릿 값이기 때문에 add-mask처럼 <code>***</code>로 나오게 된다</p>
<h3 id="다른-os">다른 OS</h3>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  fist-job:
    name: &#39;first Job&#39;
    strategy:
      matrix:
        os: [&#39;windows-latest&#39;, &#39;ubuntu-latest&#39;, &#39;macos-latest&#39;]

    runs-on: ${{ matrix.os }}

    steps:
      - name: &#39;Say Hello on ${{ matrix.os }}&#39;
        shell: bash
        run: |
          echo &quot;Hello from ${{ matrix.os }}&quot;
</code></pre>
<p>os부분에서 windows, ubuntu, macos를 각각 넣어줘서 총 3번이 다른 os에서 실행되게 해주는것이다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/26ebfd8d-4bf2-4c0b-83b3-409e96b9edff/image.png" alt="">
사진을 보면 first Job이 3개 생기고 모두 다른 os 인것을 확인할수 있다.</p>
<p>노드 버전이 까지 돌리게 하면</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  fist-job:
    name: &#39;first Job&#39;
    strategy:
      matrix:
        os: [&#39;windows-latest&#39;, &#39;ubuntu-latest&#39;, &#39;macos-latest&#39;]
        nodejs: [&#39;14.x&#39;, &#39;16.x&#39;]
    runs-on: ${{ matrix.os }}

    steps:
      - name: &#39;Setup node.js version&#39;
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.nodejs }}

      - name: &#39;Check node.js version on ${{ matrix.os }}&#39;
        shell: bash
        run: |
          echo &quot;nodejs version: $(node --version)&quot;
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8a5eca06-b91f-483a-999e-235ebed15d09/image.png" alt="">
14.x와 16.x를 해주면 총 3x2=6번을 돌리는걸 확인할수 있다.</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  fist-job:
    name: &#39;first Job&#39;
    strategy:
      matrix:
        os: [&#39;windows-latest&#39;, &#39;ubuntu-latest&#39;, &#39;macos-latest&#39;]
        nodejs: [&#39;14.x&#39;, &#39;16.x&#39;]
    runs-on: ${{ matrix.os }}

    steps:
      - name: &#39;Setup node.js version&#39;
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.nodejs }}

      - name: &#39;Run only on Windows&#39;
        if: matrix.os == &#39;windows-latest&#39;
        shell: bash
        run: |
          echo &quot;Windows Only&quot;

      - name: &#39;Run not on Windows&#39;
        if: matrix.os != &#39;windows-latest&#39;
        shell: bash
        run: |
          echo &quot;Not Windows&quot;

      - name: &#39;Check node.js version on ${{ matrix.os }}&#39;
        shell: bash
        run: |
          echo &quot;nodejs version: $(node --version)&quot;
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/3557cdd3-3e01-459e-afb9-0d7d8e8aa683/image.png" alt=""></p>
<h3 id="조건에따라-action을-실행">조건에따라 action을 실행</h3>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  fist-job:
    name: &#39;first Job&#39;
    if: github.event_name == &#39;pull_request&#39;
    strategy:
      matrix:
        os: [&#39;windows-latest&#39;, &#39;ubuntu-latest&#39;, &#39;macos-latest&#39;]
        nodejs: [&#39;14.x&#39;, &#39;16.x&#39;]
    runs-on: ${{ matrix.os }}

    steps:
      - name: &#39;Setup node.js version&#39;
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.nodejs }}

      - name: &#39;Run only on Windows&#39;
        if: matrix.os == &#39;windows-latest&#39;
        shell: bash
        run: |
          echo &quot;Windows Only&quot;

      - name: &#39;Run not on Windows&#39;
        if: matrix.os != &#39;windows-latest&#39;
        shell: bash
        run: |
          echo &quot;Not Windows&quot;

      - name: &#39;Check node.js version on ${{ matrix.os }}&#39;
        shell: bash
        run: |
          echo &quot;nodejs version: $(node --version)&quot;
</code></pre>
<p>같은 코드에서 if를 걸어 pull_request에만 실행되게 할수있다.
<img src="https://velog.velcdn.com/images/emong_96/post/1b898023-c425-469c-9f14-e0bbcdf64b8a/image.png" alt=""></p>
<p>push에 동작 되어있기 때문에 actions가 비활성화 된것을 볼수 있다.</p>
<h3 id="순서-설정">순서 설정</h3>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  build-job:
    name: &#39;build Job&#39;

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say Hello&#39;
        shell: bash
        run: |
          echo &quot;Hello World&quot;

  deploy-to-dev-job:
    name: &#39;Deployment Job to DEV&#39;
    needs:
      - build-job

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say DEV&#39;
        shell: bash
        run: |
          echo &quot;DEVELOPERS&quot;
  deploy-to-prod-job:
    name: &#39;Deployment Job to PROD&#39;
    needs:
      - build-job

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say PROD&#39;
        shell: bash
        run: |
          echo &quot;PRODUCTION&quot;
</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/3a531012-202e-4c1e-bae5-0969e07f4090/image.png" alt="">
needs를 적용하여 build-job작업이 끝난 이후에 나머지 작업이 실행되는것을 볼수 있다.</p>
<pre><code class="language-yaml">name: &#39;My First GitHub Actions Workflow&#39;

on: push

jobs:
  build-job:
    name: &#39;build Job&#39;

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say Hello&#39;
        shell: bash
        run: |
          echo &quot;Hello World&quot;

  deploy-to-dev-job:
    name: &#39;Deployment Job to DEV&#39;
    needs:
      - build-job

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say DEV&#39;
        shell: bash
        run: |
          echo &quot;DEVELOPERS&quot;
  deploy-to-qa1-job:
    name: &#39;Deployment Job to QA1&#39;
    needs:
      - deploy-to-dev-job

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say DEV&#39;
        shell: bash
        run: |
          echo &quot;DEVELOPERS&quot;
  deploy-to-qa2-job:
    name: &#39;Deployment Job to QA2&#39;
    needs:
      - deploy-to-dev-job

    runs-on: ubuntu-latest

    steps:
      - name: &#39;Say DEV&#39;
        shell: bash
        run: |
          echo &quot;DEVELOPERS&quot;
</code></pre>
<p>이런식으로 needs를 설정하면</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/44b990fa-07d4-4a58-9f1e-30c5ec5bb053/image.png" alt="">
이런 형식으로 체이닝이 가능하다.</p>
<p><a href="https://github.com/dhrudcks01/GitHub-Action">https://github.com/dhrudcks01/GitHub-Action</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 2주차 과제]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C</guid>
            <pubDate>Sat, 30 Jul 2022 08:01:04 GMT</pubDate>
            <description><![CDATA[<h2 id="과제-내용">과제 내용</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/4ad9973f-6ffc-48ab-8a6b-9d6a45c82d4c/image.png" alt=""></p>
<ul>
<li>애저 API 관리자</li>
<li>애저 펑션</li>
<li>애저 저장소 어카운트</li>
<li>애저 앱 서비스 플랜</li>
<li>애저 애플리케이션 인사이트</li>
<li>애저 로그 아날리틱스 워크스페이스</li>
</ul>
<p><a href="https://docs.microsoft.com/en-us/azure/templates/">https://docs.microsoft.com/en-us/azure/templates/</a></p>
<p>각 필요한 리소스를 Microsoft 공식 문서를 찾아보고 이름을 치면 여러게 뜨는데 template format을 찾아 보면된다.
<img src="https://velog.velcdn.com/images/emong_96/post/10f6c537-27a0-4e55-9ef4-6ca8488728aa/image.png" alt=""></p>
<p>현재 나는 fncapp-oca-krc_group이라는 리소스 그룹안에 생성을 할려고 한다
<img src="https://velog.velcdn.com/images/emong_96/post/f02ab9a6-c8c4-487e-a1c5-0c1a87d092e3/image.png" alt="">
name은 ocaproject2로 설정하고 publisherEmail은 azure을 가입한 이메일을 입력하였다.</p>
<pre><code>az deployment group create -g fncapp-oca-krc_group -f main.bicep -p name=ocaproject2 publisherEmail=dhrudcks01@naver.com</code></pre><p>을 입력하게 되면 한참을 run하다 보면 완료 됬다고 뜨게 된다
<img src="https://velog.velcdn.com/images/emong_96/post/220bd629-3a5e-4f5f-8715-7dc0aeb4814e/image.png" alt="">
생성 완료!! 
<img src="https://velog.velcdn.com/images/emong_96/post/e15edd2c-c441-48d7-861a-3ae7b7af64fd/image.png" alt="">
리소스 그룹을 들어가보면 생성이 잘되어있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 2주차 ]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-2%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-2%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 30 Jul 2022 07:21:22 GMT</pubDate>
            <description><![CDATA[<h2 id="bicep">Bicep</h2>
<p>Bicep은 애저 리소스를 배포하는데 사용되는 RM(Resource Manager)템플릿 언어로 DSL 즉, 도메인을 위해 설계되었다.</p>
<p>Bicep을 생성하기 위해서는 사전작업으로 Azure CLI와 애저 계정 구독을 해야한다.(애저 CLI로 배포를 해야하기 때문)</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/dc735c22-7ab9-47bf-b5fb-db274b2ea694/image.png" alt=""></p>
<p>다음은 Bicep 작동방식으로, Bicep 템플릿을 JSON템플릿으로 변환한다. 이 프로세스를 트랜스파일(transpilation)이라고 하며, ARM 템플릿을 중간 언어로 처리하는 프로세스다. 수동으로도 가능하지만, 자동으로 수행이된다. Bicep이 읽기 쉽고, 사용하기 편리한 이유는 예시 코드를 보면 직관적으로 알 수 있다.</p>
<h2 id="실습">실습</h2>
<p>VSC에서 bicep extestion을 설치합니다.
<img src="https://velog.velcdn.com/images/emong_96/post/3a16107c-1c2f-42d9-835f-eac6645009f9/image.png" alt=""></p>
<pre><code>az bicep upgrade

az bicep version 

az login
// Azure 계정 로그인</code></pre><p>코드를 순서대로 입력합니다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8bbfff8b-a5f8-478f-8cf4-5bb0fd6ce44e/image.png" alt=""></p>
<p>로그인이 되었다고 뜹니다.</p>
<p>그후 루트 디렉토리에서 infra라는 폴더를 생성후 안에 mian.biecp을 생성합니다.</p>
<pre><code>az bicep build -f main.bicep</code></pre><p>코드대로 빌드시켜 main.json을 생성합니다.</p>
<h3 id="bicep의-중요요소">Bicep의 중요요소</h3>
<ol>
<li>Parameter</li>
<li>Variable</li>
<li>Resource</li>
<li>Output</li>
</ol>
<p>이때 3번 Resource는 무조건 필수적인 값이다.</p>
<h3 id="빌드-및-배포">빌드 및 배포</h3>
<pre><code>az bicep build -f main.bicep
az deployment group create -n oca -g rg-oca-krc -f main.bicep -p name=oca</code></pre><p>-n : 배포하는 이름
-g : 리소스 그룹 이름</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/6388cc47-c0e5-46d3-8ca9-f1407abc3e4f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 1주차 과제]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%EA%B3%BC%EC%A0%9C</guid>
            <pubDate>Sat, 30 Jul 2022 05:44:45 GMT</pubDate>
            <description><![CDATA[<h2 id="fluent-apifluent-interface">Fluent API(fluent interface)</h2>
<ul>
<li>메소드 체이닝에 상당 부분 기반한 객체 지향 API 설계 메소드<ul>
<li>메소드 체이닝(Method)
OOP에서 여러 메소드를 이어서 호출하는 문법
메소드가 객체(this)를 반환하여 여러 메소드를 순차적으로 선언할 수 있도록 한다.
모든 메서드를 함께 연결하여 단일 명령문을 형성할 수 있는 일반적인 기술이다. 
<img src="https://velog.velcdn.com/images/emong_96/post/af023373-6abc-4e2a-9a45-5941c0b212ff/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><p>소스 코드의 가독성을 산문과 유사하게 만드는 것이 목적 
→ 즉 개발자들이 읽기 편하고, 라인 줄 줄이고, 간단하게 기술할 수 있다는 매력을 갖고 있다.</p>
</li>
<li><p>특히 인터페이스 안에 도메인 특화 언어(DSL)를 작성</p>
</li>
</ul>
<pre><code class="language-c#">var translations = new Dictionary&lt;string, string&gt;
                   {
                       {&quot;cat&quot;, &quot;chat&quot;},
                       {&quot;dog&quot;, &quot;chien&quot;},
                       {&quot;fish&quot;, &quot;poisson&quot;},
                       {&quot;bird&quot;, &quot;oiseau&quot;}
                   };

// Find translations for English words containing the letter &quot;a&quot;,
// sorted by length and displayed in uppercase
IEnumerable&lt;string&gt; query = translations
    .Where   (t =&gt; t.Key.Contains(&quot;a&quot;)) // Key 값에 a가 있는지 확인
    .OrderBy (t =&gt; t.Value.Length) // Value 값의 길이 오름차순
    .Select  (t =&gt; t.Value.ToUpper()); // Value 값을 대문자로 

// The same query constructed progressively:
var filtered   = translations.Where (t =&gt; t.Key.Contains(&quot;a&quot;));
var sorted     = filtered.OrderBy   (t =&gt; t.Value.Length);
var finalQuery = sorted.Select      (t =&gt; t.Value.ToUpper());</code></pre>
<h3 id="인터페이스와-클래스-구현일반적인-코드-vs-fluent-api-구현한-코드">인터페이스와 클래스 구현(일반적인 코드 vs fluent API 구현한 코드)</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/6a56efeb-fa99-4cd4-9028-67c714511e10/image.png" alt=""></p>
<ul>
<li>인터페이스를 구현하고 기술할 메소드를 설계</li>
<li>클래스에서 인터페이스를 상속받아 내부 변수 및 프로퍼티, 메소드를 구현</li>
</ul>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8ffed611-6de2-4a03-88de-122503089eb3/image.png" alt=""></p>
<ul>
<li>인터페이스는 반환이 void가 아닌 인터페이스 명을 기술</li>
<li>상속받은 클래스 또한 void가 아닌 인터페이스로 반환 처리 → <strong>return this를 통해 자신을 반환</strong><ul>
<li>static 으로 New() 생성자를 구현하여 객체 인스턴스를 반환</li>
</ul>
</li>
</ul>
<h3 id="인스턴스-생성">인스턴스 생성</h3>
<p><img src="https://velog.velcdn.com/images/emong_96/post/28c9ec12-e235-40ab-b203-801c3761d2a0/image.png" alt=""></p>
<ul>
<li>클래스의 인스턴스를 생성하여 객체 생성 후 각 메소드를 이용해 지역변수에 값 할당</li>
</ul>
<p><img src="https://velog.velcdn.com/images/emong_96/post/baff676b-aa99-4634-9b4c-e2877fc1aa23/image.png" alt=""></p>
<ul>
<li>static으로 생성자를 호출하는 New()를 구현하여 인스턴스를 생성한 다음 세미콜론(;)으로 구문을 닫지 않고 바로 점(.)을 통해 간결하게 값을 할당할 수 있음! </li>
</ul>
<h3 id="fluent-interface-design-pattern을-언제-사용해야-하나요">Fluent Interface Design Pattern을 언제 사용해야 하나요?</h3>
<ol>
<li>개발자가 본격적인 프로그래머가 아닌 경우 UNIT 테스트 중.</li>
<li>코드가 비즈니스 로직에 만족하는지 여부를 이해할 수 있도록 프로그래머가 아닌 사람들이 코드를 읽을 수 있기를 원할 때.</li>
<li>당신이 구성 요소 판매자이고 인터페이스를 더 단순하게 만들어 다른 사람들과 비교하여 시장에서 눈에 띄기를 원하는 경우.</li>
</ol>
<h2 id="dsl">DSL</h2>
<h3 id="사전-개념">사전 개념</h3>
<ul>
<li>DDD (도메인 주도 개발, Domain-Driven Development) 방법론
: 도메인(실세계에서 사건이 발생하는 집합 / 여기서는 비즈니스 Domain으로 유사한 업무의 집합을 의미) 중심으로 설계해 나가는 것 
: 기존의 어플리케이션 설계가 비즈니스 Domain에 대한 이해가 부족한 상태에서 설계 및 개발되었다는 반성에서 출발. → 데이터(객체) 주도 설계 탈피! 
: DDD에서는 기존의 현업에서 IT로의 일방향 소통구조를 탈피하여 현업과 IT의 쌍방향 커뮤니케이션을 매우 중요하게 생각  <ul>
<li>옷 쇼핑몰에서의 DDD Example
손님들이 주문하는 도메인(Order Domain) / 점주입장에서 옷들을 관리하는 도메인(Manage Domain) / 쇼핑몰 입장에서 월세, 관리비 등 건물에 대한 관리를 담당하는 도메인(Building Domain)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/emong_96/post/31471152-e262-4e9c-956c-f1e5c6046e50/image.png" alt="">
같은 객체가 여러 개 존재할 수 있다. 이때 <strong>Bounded</strong> <strong>Context</strong>에 따라 객체(Model)의 역할은 완벽히 바뀐다.  → 서로 다른 도메인 영역에 영향을 끼치려면 API 호출을 통해서 이루어져야.. </p>
<p>EX) 주문 도메인의 옷 == 손님들에게 팔기 위한 객체 정보 ↔ 옷을 관리하는 도메인의 옷 == 점주입장에서 관리하기 위한 객체 정보</p>
<p>각 도메인은 서비스 별로 철저히 분리 → 어플리케이션 또는 그 안의 모듈간의 의존성은 최소화하고, 응집성은 최대화 → 변경과 확장에 용이한 설계</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/41c5631c-4b63-4e28-a4e5-8ed6af6ce708/image.png" alt=""></p>
<ol>
<li>Application Layer: 주로 도메인과 Repository를 바탕으로 실제 서비스(API)를 제공하는 계층</li>
<li>Domain Layer: Entity, VO(Value Object)를 활용하여 도메인 로직이 진행되는 계층</li>
<li>Infrastructure Layer: 쉽게 말하면 외부와의 통신(ORM, DB, NoSql)을 담당하는 계층</li>
</ol>
<h3 id="그래서-dsldomain-specific-language">그래서 DSL(Domain-specific language)??</h3>
<ul>
<li><p>특정 도메인을 적용하는데 특화된 컴퓨터 언어 → 즉 특정용도에서만 사용 가능 </p>
</li>
<li><p>특정 영역의 문제 해결에는 그 영역에 맞는 특화된 도구를 사용하자는 취지. </p>
</li>
<li><p>표현 방식은 해당 도메인의 전문가(비 프로그래머)가 이해할 수 있는 형태여야 함 →  코드가 일반 자연어를 읽는 것과 같이 쉽게 이해됨</p>
<blockquote>
<p>라이브러리나 프레임워크등을
도메인 Expert 나 우리같은 일반 프로그래머에게 조차도 사용하기 쉽게 (Fluent 하게) 제공해주는것 자체,
제한된 상황 내에서 고민과 노력으로 이루어진 &quot;깔끔한 문장&quot; 으로 우리에게 제공하는 API 자체가 DSL</p>
</blockquote>
</li>
<li><p>우리 주변에 있는 DSL 
SQL, HTML, CSS , java . ANT, Maven, struts-config.xml, Seasar2 S2DAO, HQL(Hibernate Query Language), JMock- Ruby . Rails Validations, Rails ActiveRecord, Rake, RSpec, Capistrano, Cucumber</p>
</li>
</ul>
<h3 id="dsl의-종류">DSL의 종류</h3>
<p>DSL은 내부 DSL과 외부 DSL로 구분할 수 있다. </p>
<h4 id="1-내부-dsl">1. 내부 DSL</h4>
<ul>
<li><p>특수한 목적을 위해 제한된 방법으로 호스트 언어를 사용하는 방식, 자체적으로 의존하는 무언가를 만드는 경우에 해당</p>
</li>
<li><p>임베디드 DSL, Fluent Interfaces 라고도 한다.</p>
</li>
<li><p>내부 DSL에서는 API와 DSL의 경계가 모호해 비슷하게 생각하는 경향이 있음 → 좀 더 <strong>일반 사용자가 알아보기 쉬운 API가 내부 DSL로 생각해도 될 듯 함</strong></p>
</li>
<li><p>사용하던 도구를 그대로 이용할 수 있음,  처리 결과를 쉽게 예측할 수 있음
EX) Ruby, Smalltalk 등</p>
</li>
</ul>
<h4 id="2-외부-dsl">2. 외부 DSL</h4>
<p>호스트 언어가 아닌 다른 언어에서 생성된 DSL이다.</p>
<ul>
<li><p>대부분 자체적인 문법을 가지지만 기존 언어의 문법을 쓰는 예도 있다.</p>
</li>
<li><p>외부 DSL에서는 DSL과 범용 언어(GPL : General Purpose Language)과의 경계가 모호해지는 경향이 있다. 그 차이는 언어 작성자와 언어 사용자의 목적에 있다. 특정 영역에서 언어의 작성자가 아닌 사용자의 목적에 부합하는, 이해를 할 수 있으면 외부 DSL이다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/emong_96/post/2483e44d-4dd6-4983-a5a6-7fecfdfed542/image.png" alt=""></p>
<ol>
<li>XML DSL == 외부 DSL (xml이라는 언어에서 작성된 DSL)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/emong_96/post/d1c8e2d9-ce46-4f53-b7e0-41833c1ba8a7/image.png" alt=""></p>
<ol start="2">
<li>위의 xml이 보기 불편해서 원하는 대로 문법 작성 → DSL (단순 string 집합체 이기 때문에 파싱하기 위한 파서[인터프리터 혹은 컴파일러 내부의 구문 분석하는 프로그램]를 만들어서 써야함)</li>
</ol>
<p>→ 만약 이 파서가 내가 쓰는 언어 내에서 그대로 쓸 수 있다면? 내가 쓰는 언어의 파서가 알아서 구문 분석까지 해준다면? == 내부 DSL</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/96a6dbbf-e363-4185-9999-878f8e9e24ba/image.png" alt=""></p>
<ol start="3">
<li>현실적으로 내가 쓰는 언어 내에서 그대로 사용하는게 best지만 파써를 직접 구현하기보다 적당한 DSL을 쓰는 것이 좋다. 
→ 2번의 코드를 루비 문법으로 작성한 코드 == 내부 DSL </li>
</ol>
<p><img src="https://velog.velcdn.com/images/emong_96/post/aedf89b7-ceb5-4c11-ab62-d9124bd547e6/image.png" alt=""></p>
<ol start="4">
<li>누가 봐도 자바 코드 → 내가 원하는 문장 자체를 쓴 것은 아니지만 fluent하게 표현함 → 이것도 DSL </li>
</ol>
<h2 id="fluent-api를-이용한-dsl-작성">Fluent API를 이용한 DSL 작성</h2>
<p>애저 펑션의 첫 엔트리 포인트에서 만나는 부분</p>
<pre><code class="language-c">public static IFunctionFactory Factory { get; set; } = new FunctionFactory(new AppModule());

[FunctionName(nameof(XmlToXmlMapperHttpTrigger))]
public static async Task&lt;HttpResponseMessage&gt; Run(
    [HttpTrigger(AuthorizationLevel.Function, &quot;post&quot;, Route = &quot;mappers/xml/xml&quot;)] HttpRequestMessage req,
    ILogger log)
{
    return result = await Factory.Create&lt;IXmlToXmlMapperFunction, ILogger&gt;(log)
                                 .InvokeAsync&lt;HttpRequestMessage, HttpResponseMessage&gt;(req)
                                 .ConfigureAwait(false);
}</code></pre>
<p><img src="https://velog.velcdn.com/images/emong_96/post/ff87f76f-2a5b-4c02-8c22-8fd9819a2b76/image.png" alt=""></p>
<ul>
<li><strong>Fluent API를 이용한 메소드 체이닝을 통해 위의 그림과 같은 워크플로우를 문자 그대로 자연스럽게(Fluently) 표현하고 있음</strong></li>
<li>일단 사용자가 애저 펑션을 호출하면 1) 서비스 로케이터 팩토리를 생성하고, 2) 팩토리는 펑션 인스턴스를 생성한 후, 3) 해당 펑션 인스턴스는 지정된 메소드를 실행시켜 결과값을 받아 반환하는 아주 간단한 워크플로우
→ 이 모든 것을 Fluent API를 이용한 메소드 체이닝을 통해 구현했다.</li>
<li><strong>메소드 이름 역시 <code>Create, InvokeAsync</code>  와 같이 상당히 직관적</strong></li>
<li>즉, <strong>메소드 이름을 보고 곧바로 이 메소드가 어떤 역할을 하게 되는지 유추가 가능한 데, 이걸 거창하게 얘기하면 &quot;유비쿼터스 언어를 이용해서 DSL을 작성한 것&quot; 이라고도 볼 수 있다</strong>.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OCA] 파워플랫폼 챌린저스 - 1주차 ]]></title>
            <link>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/OCA-%ED%8C%8C%EC%9B%8C%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B1%8C%EB%A6%B0%EC%A0%80%EC%8A%A4-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sat, 30 Jul 2022 04:52:15 GMT</pubDate>
            <description><![CDATA[<h2 id="내용">내용</h2>
<p><img src="https://velog.velcdn.com/images/emong_96/post/9c932f96-f50a-4cdd-aed1-de6fb88d976e/image.png" alt=""></p>
<p>발대식이 끝난후 7/12일 첫번째 워크샵을 진행하게 되었다. 
워크샵을 준비하기 위한 개발환경이 필요하였다.
원래 발대식때 개발환경을 다같이 맞춰서 진행하려고 했는데
발대식때 설명하는 시간이 너무 길어져서 개발환경은 세팅하지 못하여 수업전에 미리 해오기로 했습니다!</p>
<h2 id="준비-사항">준비 사항</h2>
<h3 id="계정-생성">계정 생성</h3>
<ol>
<li><p>M365 개발자 계정 : <a href="https://study.fusiondev.kr/m365/m365-dev-setup">https://study.fusiondev.kr/m365/m365-dev-setup</a></p>
</li>
<li><p>파워 플랫폼 개발자 계정 : <a href="https://study.fusiondev.kr/pp/pp-dev-setup">https://study.fusiondev.kr/pp/pp-dev-setup</a></p>
</li>
<li><p>NHN 클라우드 계정 : <a href="https://toast.com/">https://toast.com/</a></p>
</li>
</ol>
<h3 id="개발-환경-설정">개발 환경 설정</h3>
<ol>
<li>.NET 6 SDK : <a href="http://dot.net">http://dot.net</a></li>
<li>Visual Studio : <a href="http://visualstudio.com">http://visualstudio.com</a></li>
<li>Visual Studio Code : <a href="http://visualstudio.com">http://visualstudio.com</a></li>
<li>Azure CLI : <a href="http://docs.microsoft.com/cli">http://docs.microsoft.com/cli</a></li>
<li>PowerShell : <a href="http://docs.microsoft.com/powershell">http://docs.microsoft.com/powershell</a></li>
<li>Azure Function Core Tools : <a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Clinux%2Ccsharp%2Cportal%2Cbash">https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Clinux%2Ccsharp%2Cportal%2Cbash</a></li>
</ol>
<p>이후 다음과 같이 .NET 및 Azure Function 버전을 확인합니다.</p>
<pre><code>
dotnet --version
// 6.0.301

func --version
// 4.0.4629

which func
// /usr/local/bin/func

which dotnet
// /usr/local/share/dotnet/dotnet</code></pre><h3 id="azure-function-생성cli">Azure Function 생성(CLI)</h3>
<pre><code>func init</code></pre><p>후에 터미널에 나오는 옵션중</p>
<ol>
<li>dotnet 선택 -&gt; 1 </li>
<li>C# 선택 -&gt; 1</li>
</ol>
<pre><code>func new</code></pre><p>후에 터미널에 나오는 옵션중</p>
<ol>
<li>HttpTrigger 선택 -&gt; 1</li>
<li>함수 이름 설정 -&gt; 원하는 함수이름(실습에는 PingHttpTrigger 로 설정)</li>
</ol>
<p>이후 디렉토리를 보면 새로운 파일들이 생성 되었습니다.
<code>.csproj</code> 파일을 열어 <code>&lt;TargetFramework&gt;</code>, <code>&lt;AzureFunctionsVersion&gt;</code> 태그 안의 값을 확인합니다.
<code>&lt;TargetFramwork&gt;</code> - net6.0
<code>&lt;AzureFunctionsVersion&gt;</code> - v4</p>
<h3 id="네임-수정">네임 수정</h3>
<p>.csproj 파일에 추가합니다.
<code>&lt;AzureFunctionsVersion&gt;</code> 태그 뒤에 추가합니다.</p>
<pre><code class="language-c#">&lt;AssemblyName&gt;OCAProject&lt;/AssemblyName&gt;
&lt;RootNamespace&gt;OCAProject&lt;/RootNamespace&gt;</code></pre>
<p>이후 PingHttpTrigger.cs 파일을 열어 소스코드 최상단의 namespace를 수정합니다.</p>
<pre><code class="language-c#">namespace OCAProject</code></pre>
<p>오류줄은 터미널에 </p>
<pre><code>dotnet build
func start</code></pre><p>을 입력합니다.
정상적으로 동작을 한다면 <a href="http://localhost:7071/api/ping">http://localhost:7071/api/ping</a> 에 접속하여 아래의 문구가 나타나는지 확인합니다.</p>
<blockquote>
<p>This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.</p>
</blockquote>
<p>이후 <a href="http://localhost:7071/api/ping?name=OCA">http://localhost:7071/api/ping?name=OCA</a> 에 접속하여, 위의 문구가 다음과 같이 변경되는지 확인합니다.</p>
<blockquote>
<p>Hello, OCA. This HTTP triggered function executed successfully.</p>
</blockquote>
<h3 id="클래스-수정">클래스 수정</h3>
<p>PingHttpTrigger 클래스를 다음과 같이 수정합니다.</p>
<pre><code class="language-c">[FunctionName(nameof(PingHttpTrigger))] 
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, &quot;get&quot;, Route = &quot;ping&quot;)] HttpRequest req,
ILogger log)</code></pre>
<p>그리고 다음의 코드를 삭제합니다.</p>
<pre><code class="language-c">string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;</code></pre>
<p>다시 dotnet build 을 통해 다시 한번 빌드하고,</p>
<p>func start 를 통해 어플리케이션을 실행하면, <a href="http://localhost">http://localhost</a> 환경에서 <code>responseMessage</code> 변수의 값이 출력되는 것을 확인할 수 있습니다. </p>
<p><a href="http://localhost">http://localhost</a> 주소 뒤에 query string ex)?name=emong 을 추가하여 요청을 보내면, 쿼리 스트링 값이 포함된 문자열 응답을 확인할 수 있습니다.</p>
<h3 id="응답-객체-생성">응답 객체 생성</h3>
<p>Models라는 디렉토리를 생성후 내부에 ResponseMessage.cs 파일을 생성하고 아래와 같이 내용을 작성합니다.</p>
<pre><code class="language-c">public class ResponseMessage {
    [JsonProperty(&quot;response_message&quot;)] // Json 응답 객체의 key 를 변경. 기본적으로는 파스칼 케이싱을 캐멀 케이싱으로 변환.
    public string Message { get; set; }
}</code></pre>
<p>그후 Services라는 디렉토리를 생성후 내부에 IMyService.cs라는 인터페이스 파일과 해당 인터페이스를 상속받아 구현하는 MyService.cs라는 파일을 생성하고 아래와 같이 내용을 작성합니다. </p>
<pre><code class="language-c">public interface IMyService {
        string GetMessage(string name);
}

public class MyService : IMyService {
    public string GetMessage(string name) {
        string responseMessage = string.IsNullOrEmpty(name)
            ? &quot;This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.&quot;
            : $&quot;Hello, {name}. This HTTP triggered function executed successfully.&quot;;

        return responseMessage;
    }
}</code></pre>
<p>이후 PingHttpTrigger.cs 파일의 Run method 수정합니다.</p>
<pre><code class="language-c">var service = new MyService();
var result = service.GetMessage(name);

var res = new ResponseMessage() { Message = result };</code></pre>
<p>결합도를 낮추기 위해 다음과 같은 읽기 전용 멤버 변수와 생성자를 PingHttpTrigger 클래스 최상위에 생성합니다.</p>
<pre><code class="language-c">private readonly IMyService _service;

public PingHttpTrigger(IMyService service) {
    this._service = service ?? throw new ArgumentNullException(nameof(service));
}</code></pre>
<p>그후 코드를 다음과 같이 수정합니다.</p>
<pre><code class="language-c">var result = this._service.GetMessage(name);

var res = new ResponseMessage() { Message = result };</code></pre>
<h3 id="패키지-추가-설치">패키지 추가 설치</h3>
<pre><code>dotnet add package Microsoft.Azure.Functions.Extensions
dotnet add package Microsoft.Extensions.Http</code></pre><p>설치후 <code>&lt;ItemGroup&gt;</code>태그를 .csproj 파일에서 확인하여 추가 패키지가 설치되었는지 확인합니다.</p>
<p>그후 Startup.cs란 파일을 프로젝트 루트 디렉토리에 생성하고 작성합니다.</p>
<pre><code class="language-c">using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(OCAProject.Startup))]
namespace OCAProject
{
    public class Startup: FunctionsStartup {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddScoped&lt;IMyService, MyService&gt;();
        }
    }
}</code></pre>
<p>Models, Services 디렉토리를 만드는 등 갑자기 너무 많은 것을 생성 및 수정하였는데요.</p>
<p>이는 객체 지향 프로그래밍의 원칙들을 준수하기 위함입니다.</p>
<p>우선, 객체 지향 프로그래밍에서는 하나의 클래스가 하나의 기능만을 담당하도록 하는 것이 권장됩니다.</p>
<p>따라서 각각의 cs파일들이 하나의 클래스만을 담을 수 있도록 디렉토리 및 파일을 생성하고 수정하였습니다.</p>
<p>또한, 각각의 클래스들이 강하게 결합되어 의존하지 않는 것이 권장됩니다. 따라서 종속성 주입을 해주었습니다.</p>
<h3 id="openapi-등록">OpenApi 등록</h3>
<p>터미널을 통해 패키지를 추가합니다.</p>
<pre><code>dotnet add package Microsoft.Azure.WebJobs.Extensions.OpenApi</code></pre><p>.csproj 파일에서 패키지가 잘 설치되었는지 확인합니다.
다음과 같이 PingHttpTrigger 클래스를 수정합니다.
OpenApi에 등록하기위해 attribute 를 추가해줍니다.</p>
<pre><code>[FunctionName(nameof(PingHttpTrigger))]

// OpenApi에 등록합니다.
[OpenApiOperation(operationId: &quot;Ping&quot;, tags: new[] { &quot;greeting&quot; })]
// OpenApi credential을 설정합니다.
[OpenApiSecurity(schemeName: &quot;function_key&quot;, schemeType: SecuritySchemeType.ApiKey, Name = &quot;x-functions-key&quot;, In = OpenApiSecurityLocationType.Header)]
// OpenApi parameter를 설정합니다.
[OpenApiParameter(name: &quot;name&quot;, In = ParameterLocation.Query, Required = true, Description = &quot;Name of the person&quot;)]
// OpenApi response를 설정합니다.
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: &quot;application/json&quot;, bodyType: typeof(ResponseMessage), Description = &quot;response description&quot;)]</code></pre><p>namspace가 소스 파일에 있는지 확인합니다.</p>
<p>그후 다시 빌드를 진행합니다. </p>
<pre><code>dotnet build
func start</code></pre><p><a href="http://localhost:7071/api/swagger/ui">http://localhost:7071/api/swagger/ui</a> 로 접속하면 아래와 같은 화면이 나타납니다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/2047cfca-8766-40b5-a469-2ba9be95d970/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 66~70일차 스탠바잇 프로젝트 1주차]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-6670%EC%9D%BC%EC%B0%A8-%EC%8A%A4%ED%83%A0%EB%B0%94%EC%9E%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-6670%EC%9D%BC%EC%B0%A8-%EC%8A%A4%ED%83%A0%EB%B0%94%EC%9E%87-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Fri, 29 Jul 2022 05:04:21 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-6670일차">수업 66~70일차</h2>
<h3 id="이번주-부터는-엘리스에서-시행하는-팀원들과-함께-하는-프로젝트-2차를-시작-하게-되었다-프론트-엔드-캠프지만-백엔드를-희망-하는-나로써는-이번에도-백엔드-파트를-담당하게-되었다">이번주 부터는 엘리스에서 시행하는 팀원들과 함께 하는 프로젝트 2차를 시작 하게 되었다 프론트 엔드 캠프지만 백엔드를 희망 하는 나로써는 이번에도 백엔드 파트를 담당하게 되었다~!</h3>
<h2 id="내용">내용</h2>
<p>월요일부터 수요일까지 3일간 프로젝트 안내 받고 프로젝트를 기획하도록 하였다!
어떤 주제의 프로젝트를 할것인지, 어떤 기술을 쓸려고 하는지, 어떤 방식으로 구현을 할지 정하는 시간이였다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/32584125-536e-44c0-a3bb-eacea4beae2d/image.png" alt=""></p>
<p>팀원의 투표결과 음식점을 예약하는 프로젝트를 하기로 하였구
이름을 고민하다가 다른거는 기다리지 말고 먹을 준비만 하라는 의미에서
StandByeat으로 정했다!</p>
<p>그리고 노션과 피그마에 어떻게 만들지 틀을 그리게 되었다!
<img src="https://velog.velcdn.com/images/emong_96/post/a0331c20-7d77-4a62-9bcc-c87e94622d0f/image.png" alt=""></p>
<p>노션페이지를 새로 만들고 팀원의 스크럼을 적고 어떤 기능을 구현할지, 관련된 사이트는 어디를 참고 할것인지 등등 팀원에게 공유할 내용들을 작성하였다~!</p>
<p><a href="https://www.notion.so/2-792e607f88f7495fbad136e9168601f2">https://www.notion.so/2-792e607f88f7495fbad136e9168601f2</a>
링크는 이렇게 된다~</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/6dfd9518-6377-4ead-8242-267912ea965d/image.png" alt="">
피그마도 프론트분들이 작성하여 이런 식으로 페이지를 구성할꺼다 라는 화면을 작성하였다!</p>
<p><a href="https://www.figma.com/file/ey4pOq90z2jHRkivXGDeLq/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A7%81-%EC%98%88%EC%95%BD?node-id=0%3A1">https://www.figma.com/file/ey4pOq90z2jHRkivXGDeLq/%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A7%81-%EC%98%88%EC%95%BD?node-id=0%3A1</a></p>
<p>이번에는 바닐라로 js 코딩하는게아니라 리액트를 쓰기 때문에 클라이언트를 열고 서버를 열어서 두개를 연결시켜야하는데 그방법을 찾기위해서 proxy라는 걸 알게 되었다. 그래서 리액트 json쪽에 proxy포트를 적어주고 서버와 연결했는데 오피스 아워때 코치님께 물어본 결과 그럴 필요없이 두개의 서버를 열어주고 클라이언트 쪽에서는 서버의 API만 가져오면 되는것이였다...!
<img src="https://velog.velcdn.com/images/emong_96/post/8dcbe4db-e7c6-476a-81d4-86b4005ee48b/image.png" alt=""></p>
<p>그결과 리액트인 3000포트에서 5000포트인 DB에서 값을 가져올수 있게 되었다!
<img src="https://velog.velcdn.com/images/emong_96/post/29013533-7972-4903-9ea0-4cc2c41fdb7b/image.png" alt=""></p>
<p>DB에서 Data.id을 가지고와서 보여주게 된다.
포스트맨도 연동하여 DB에 값을 넣고 회원가입까지는 구현하게 되었다.
앞으로 2주간 더 발전된 기능을 추가하고 실력이 늘었으면 좋겠다!</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/c60f7357-e6a0-4568-8321-ad4398d2f0a1/image.png" alt=""></p>
<p>스키마도 작성하여 스프레드 시트에 공유하고 작성하였다</p>
<p><a href="https://docs.google.com/spreadsheets/d/18wSjjrUqZakAKz6wTBfXr6sPJwrnJSJTrbowwTkci1I/edit#gid=276855386">https://docs.google.com/spreadsheets/d/18wSjjrUqZakAKz6wTBfXr6sPJwrnJSJTrbowwTkci1I/edit#gid=276855386</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 61~65일차 Next.js]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-6165%EC%9D%BC%EC%B0%A8-Next.js</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-6165%EC%9D%BC%EC%B0%A8-Next.js</guid>
            <pubDate>Fri, 29 Jul 2022 03:38:43 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-6165일차">수업 61~65일차</h2>
<h3 id="이번주는-배웠던거를-복습하는-차원으로-공부를-했고-이론보다는-실습-위주여서-한주를-통합하도록-하겠습니다-다음주에는-프로젝트">이번주는 배웠던거를 복습하는 차원으로 공부를 했고 이론보다는 실습 위주여서 한주를 통합하도록 하겠습니다~ 다음주에는 프로젝트..!</h3>
<h2 id="이론">이론</h2>
<ul>
<li>CSR: 초기 로딩은 느리지만, 사용자에게 좋은 사용 경험을 제공
SSR: SEO를 손쉽게 적용할 수 있음
SSG: SSR처럼 완성된 HTML을 받아오지만, 해당 HTML은 빌드 타임에 생성됨
next.js: React에 SSR 개념을 적용한 프레임워크
Code Splitting: 각 페이지에서 필요한 JS만 import할수 있도록 자동분리
배포: 사용자들이 생성된 프론트앱에 접근할수 있도록 하는것
Server Rendering: React, Vue, Angular 등 자바스크립트 프레임워크가 나이기 이전 초기 웹 환경에서는 모든 페이지를 서버에서 빌드
Client Side Rendering: Ajax 등의 기술, 자바스크립트 프레임워크를 활용하여, 데이터를 받아 자바스크립트로 페이지를 동적으로 만들 수 있게 됨
Server Side Rendering: 서버에서 자바스크립트를 이용해 페이지를 미리빌드
웹 퍼포먼스: 웹 페이지가 로드되고 유저와 상호작용하는 모든 것들을 측정
Time To First Byte: 페이지 요청 후, 처음 데이터가 도착하기까지 걸리는 시간
First Contentful Paint: 페이지에 진입하고부터, 브라우저가 어떤 DOM Content를 만들 때까지 걸리는 시간
Time To Interactive: 웹페이지 진입 후, 유저가 클릭, 스크릭, 인풋 등의 행위를 하기까지 걸리는 시간
React DOM Server: ReactDOMServer를 활용하여, 특정 React Component를 HTML로 빌드
renderToString: React Component를 HTMl로 변환함
CSS module: class,id 등에 random string을 달아주기 때문에 선택자가 겹칠 우려가 없음
UI framework: 이미 다 만들어져 있어서 간편하고 쉽게 쓰기에 좋음
CSS framework: 거대한 CSS 파일 하나를 가져오는 것
CSS-in-JS library: 따로 CSS 파일 만들것 없이 JSX파일 안에서 스타일링 까지 해결 가능함
javascript template literal: 문자열 안에서 JS표현식을 사용할수 있게 하는 문법
Styled Component를 이용하여 트랙카드, 탭, 검색창을 만들어 보며 UI제작 기술 숙련도를 증진합니다.
API호출을 하여 데이터를 가져오고, Styled Component를 이용하여 페이지네이션 UI를 만들어 목표한 UI를 완성합니다.</li>
</ul>
<h2 id="nextjs">Next.js</h2>
<blockquote>
<p>Next.js 는 React 라이브러리의 프레임워크이다
공식 문서에서는 하이브리드 정적 및 서버 렌더링, TypeScript 지원, 스마트 번들링 등 개발하는데 필요한 많은 기능을 제공한다 한다</p>
</blockquote>
<p>처음 Next.js 마주치는 입장으로써, 이미 React도 나름 잘 되있는게 아니었어? 라는 궁금증에 마주치기 마련이다.</p>
<p>우선, Next.js는 React의 프레임워크 인 만큼, HTML, CSS, JavaScript와 React에 대한 이해도는 필히 요구한다.</p>
<p>React이 어플리케이션을 만드는데 있어, 자유도가 높은 만큼, third-party tools 와 solution들은 흘러 넘친다. 다른 측면으로는, 바닥 부터 어플리케이션을 만들 때는 꽤나 많은 비용(시간)이 든다.</p>
<p>여기서 Next.js는 React어플리케이션을 만드는데 필요한 tooling 과 configuration을 제공하고, structure, feature, optimiztion 도 함께 제공한다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/7544b4ce-2a0a-4f6e-a71b-c10ec069f1aa/image.png" alt=""></p>
<h3 id="nextjs-쓰는-이유">Next.js 쓰는 이유</h3>
<p>가장 큰 이유는 SEO(Search Engine Optimization)을 위한 SSR(Server-Side Rendering) 을 가능하게 하기 위해서이다.</p>
<blockquote>
<p>SEO란 검색 엔진으로부터 웹사이트나 웹페이지에 대한 웹사이트 트래픽의 품질과 양을 개선하는 과정이다</p>
</blockquote>
<blockquote>
<p>SSR란 SEO를 최적화하는 렌더링 기법으로, 페이지를 이동할 때마다 새로운 페이지를 요청하고, 모든 템플릿은 서버 연산을 통해서 렌더링하고 완성된 페이지 형태로 응답하는 것이다.
첫 렌더링된 HTML을 클라이언트에게 제공하기 때문에 초기로딩속도를 많이 줄여줄 수 있다. 자바스크립트 파일을 불러오고 렌더링 작업이 완료되기 전에 사용자가 사이트 컨텐츠를 이용할 수 있다.
다만, 프로젝트의 복잡도가 증가하고, 페이지 이동시 화면이 깜빡 거리며, 서버 렌더링에 따른 부하가 발생한다.</p>
</blockquote>
<blockquote>
<p>CSR란 반대로 클라이언트에서 렌더링을 하는 방식으로, 첫 요청시 한페이지만 불러오게 된다(SPA). 그 후, 사용자의 행동에 필요한 부분만 읽기 때문에 더욱 빠른 인터렉션을 기대 할 수 있다. 즉, 필요한 부분만 리로딩 없이 서버로 부터 받아와서 화면을 갱신한다.
다만, 초기 구동속도가 느리며, 검색엔진 최적화가 어렵다.</p>
</blockquote>
<p>위 개념을 본 뒤 아래 두 그림을 보자.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/c5399e25-10c5-4bd9-87ae-066118d44a20/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/8713e71d-2d61-4977-b664-1a5eb38a8f16/image.png" alt=""></p>
<p>차이점은 간단하다. 유저가 인터렉티브하게 다룰 수 있는 어플리케이션이 등장하기 전에 정적인 HTML을 보여주는가? 아닌가? 에 따른 결과값이다. 쉽게 말해, 사용자는 모든 컨텐츠 및 기능이 로딩되기 전에, HTML을 보면서 더욱 나은 경험을 줄 수 있다. Pre-rendering을 통해 SSR과 CSR의 장점, 그리고 그 둘의 혼용을 활용할 수 있다.</p>
<h3 id="그-이외의-장점">그 이외의 장점?</h3>
<p>많은 기능들을 개발자들에게 제공한다고 호언장담한 만큼, 다음과 같은 기능들을 제공한다.</p>
<h4 id="직관적인-페이지-기반-라우팅-시스템">직관적인 페이지 기반 라우팅 시스템</h4>
<p>프로젝트 최 상단의 /page에서 컴포넌트를 export하면, 폴더명이 페이지의 route가 된다.</p>
<h4 id="페이지간-빠르고-매끄러운-전환을-위한-client-side-navigation">페이지간 빠르고 매끄러운 전환을 위한 client-side Navigation</h4>
<p><code>&lt;Link /&gt;</code> 컴포넌트를 통해 페이지간 빠르고 매끄러운 이동을 가능하게 한다. HTML의 <code>&lt;a /&gt;</code> 태그와 달리 페이지를 리로딩하지 않고도 페이지간 이동이 가능하고, Link 컴포넌트가 뷰포트에 보였을 때 관련 페이지를 백그라운드에서 미리 가져다 놓기 때문에 사용자가 링크를 클릭했을 때 매우 빠르게 해당 페이지로 이동 할 수 있게 해준다.</p>
<h4 id="code-splitting">Code Splitting</h4>
<p>웹페이지가 처음 로딩될 때, JavaScript payload를 보내는 것이 아닌, 번들을 여러 조각으로 필요한 부분만 전송해주는 방식으로 어플리케이션의 로드 타임을 줄여준다.</p>
<h2 id=""></h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 60일차 화이트 & 블랙박스 테스팅, Unit Testing, Integration Testing, End-to-end Testing]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-60%EC%9D%BC%EC%B0%A8-%ED%99%94%EC%9D%B4%ED%8A%B8-%EB%B8%94%EB%9E%99%EB%B0%95%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8C%85-Unit-Testing-Integration-Testing-End-to-end-Testing</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-60%EC%9D%BC%EC%B0%A8-%ED%99%94%EC%9D%B4%ED%8A%B8-%EB%B8%94%EB%9E%99%EB%B0%95%EC%8A%A4-%ED%85%8C%EC%8A%A4%ED%8C%85-Unit-Testing-Integration-Testing-End-to-end-Testing</guid>
            <pubDate>Fri, 22 Jul 2022 05:13:15 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-60일차">수업 60일차</h2>
<h2 id="이론">이론</h2>
<ul>
<li>코드 테스트: 코드를 작성하고 나면, 원하는 대로 동작하는지 알기 위해 테스트를 함
화이트 박스 테스팅: 컴포넌트 내부 구조를 미리 안다고 가정하고 테스트 코드를 작성
블랙박스 테스팅: 컴포넌트 내부 구조를 모른채 어떻게 동작하는지에 대한 테스트
Unit Testing: 다른 부분과 분리된 작은 코드를 만들고 그것을 테스트함
Integration Testing: 앱의 특정 부분이 잘 동작하는지를 테스트 하는 경우
End-to-end Testing: 유저가 어떤 시나리오를 가지고 그 시나리오의 end-to-end로 잘 동작하는지 테스트함
jest: Facebook에서 오픈소스화한 테스팅 프레임워크
Assertion matchers: jest는 풍부한 matcher를 제공하여, 여러 상황에서 match를 체크함
mock funcrions: mock funcrion을 만듦
Lifecycle funcrions: 각 테스트의 시작과 끝, 전체 테스트의 시작과 끝에 원하는 작업을 할 수 있음
Snapshot Testing: 특정 함수, 모듈, 컴포넌트 등의 결과를 serializable한 형태의 Snapshot으로 저장하고, 추후 변경이 발생했을때 이전의 Snapshot과 새로운 Snapshot을 비교하여 변경이 발생 했는지 추측함
react-testing-library: 테스트가 소프트웨어 사용되는 모습을 닮을수록, 테스트를 더욱 신뢰할 수 있게됨
user-event: 내장 이벤트 함수인 fireEvent, createEvent를, 좀더 직관적이고 범용적으로 사용할 수 있도록 만든 라이브러리</li>
</ul>
<h2 id="화이트박스-테스트white-box-test">화이트박스 테스트(White Box Test)</h2>
<ul>
<li>개발자 중심</li>
<li>Test 과정 초기 적용. 논리적 경로. 프로그램 흐름 점검</li>
<li>모듈 안 동작을 직접 관찰</li>
<li>모든 문장을 한 번 이상 실행</li>
</ul>
<h3 id="화이트박스-테스트의-종류">화이트박스 테스트의 종류</h3>
<h4 id="기초-경로-검사-base-path-testing">기초 경로 검사 (Base Path Testing)</h4>
<ul>
<li>대표적인 화이트박스 테스트 기법</li>
<li>테스트 케이스 설계자가 절차적 설계의 논리적 복잡성을 측정할 수 있게 해주는 테스트 기법, 테스트 측정 결과는 실행 경로의 기초를 정의하는 데 지침으로 사용</li>
</ul>
<h4 id="제어-구조-검사-control-structure-testing">제어 구조 검사 (Control Structure Testing)</h4>
<ul>
<li>조건 검사(Condition Testing): 프로그램 모듈 내에 있는 논리적 조건을 테스트하는 테스트 케이스 설계 기법</li>
<li>루프 검사(Loop Testing): Loop 구조에 초점을 맞춰 실시하는 테스트 케이스 설계 기법</li>
<li>데이터 흐름 검사(Data Flow Testing): 프로그램에서 변수의 정의와 변수 사용의 위치에 초점을 맞춰 실시하는 테스트 케이스 설계 기법</li>
</ul>
<h2 id="블랙박스-테스트black-box-test">블랙박스 테스트(Black Box Test)</h2>
<ul>
<li>기능, 사용자 중심</li>
<li>소프트웨어가 수행할 특정 기능을 알기 위해서 각 기능이 완전히 작동되는 것을 입증하는 테스트</li>
<li>사용자의 요구사항 명세를 보면서 주로 구현된 기능을 테스트</li>
<li>인터페이스에서 실시</li>
<li>테스트 과정의 후반부에서 적용</li>
</ul>
<h3 id="블랙박스-테스트의-종류">블랙박스 테스트의 종류</h3>
<h4 id="동치-분할-검사-equivalence-partitioning-testing">동치 분할 검사 (Equivalence Partitioning Testing)</h4>
<ul>
<li>입력 자료에 초점을 맞춰 테스트 케이스를 만들고 검사하는 방법으로 동등 분할 기법이라고도 함</li>
<li>입력 데이터의 영역을 유사한 도메인별로 유횻값 / 무횻값을 그룹핑하여 나누어서 검사</li>
</ul>
<h4 id="경계값-분석-boundary-value-analysis">경계값 분석 (Boundary Value Analysis)</h4>
<ul>
<li>입력 자료에만 치중한 동치 분할 기법을 보완하기 위한 기법이다.</li>
<li>입력 조건의 중간값보다 경계값에서 오류가 발생될 확률이 높다는 점을 이용</li>
</ul>
<h4 id="원인-효과-그래프-검사-cause-effect-graph">원인-효과 그래프 검사 (Cause-Effect Graph)</h4>
<ul>
<li>입력 데이터 간의 관계와 출력에 영향을 미치는 상황을 체계적으로 분석한 다음 효용성이 높은 테스트 케이스를 선정하여 검사하는 기법.</li>
</ul>
<h4 id="오류-예측-검사-error-guessing">오류 예측 검사 (Error Guessing)</h4>
<ul>
<li>과거의 경험이나 확인자의 감각으로 테스트하는 기법</li>
<li>다른 블랙 박스 테스트 기법으로는 찾아낼 수 없는 오류를 찾아냄</li>
</ul>
<h4 id="비교-검사-comparison-testing">비교 검사 (Comparison Testing)</h4>
<ul>
<li>여러 버전의 프로그램에 동일한 테스트 자료를 제공하여 동일한 결과가 출력되는지 테스트하는 기법.</li>
</ul>
<h2 id="unit-testing">Unit Testing</h2>
<ul>
<li>작은 양의 코드가 실행되고 제 역할 하는지 확인함</li>
<li>앱 속 하나의 컴포넌트를 확인함</li>
<li>module 특화</li>
<li>scope가 매우 좁은 편: 하나의 클래스만 확인하는 경우도 있음</li>
<li>unit test 밖에 있는 코드에 dependency가 없어야 함 ➡ 관심사 분리!</li>
<li>unit testing은 더 상세하게 나눠져있지 않음</li>
<li>각자의 unit들의 testing의 집중하지 서로 상호작용할 때의 문제점까지 짚지 않음</li>
<li>White Box Testing Type에 포함되어 있음</li>
</ul>
<h2 id="integration-testing">Integration Testing</h2>
<ul>
<li>각자의 모듈들이 서로 통합했을 때의 testing ➡ 통합 됐을 때 어떤 문제들이 발생하는지</li>
<li>앱 전체의 testing</li>
<li>interface 특화</li>
<li>integration testing은 DB, 하드웨어 등 외부 시스템을 의존, 참조</li>
<li>unit test 다음, 그리고 system test 전에 하는 테스트</li>
<li>integration testing은 top-down, bottom-up 등 다양한 type들로 나눠짐</li>
<li>Black Box, White Box Testing에 포함되어 있음</li>
</ul>
<h2 id="end-to-end-test">End-To-End Test</h2>
<ul>
<li>크롬 브라우저를 띄운 다음, 내가 만든 검색페이지로 들어가서 검색을 해보고 검색한 내용이 제대로 나오는지 화면상에서 확인하거나 직접 회원가입을 해보고 회원가입후에 로그인 되는지 직접 브라우저 상에서 값을 입력해서 테스트 하는방법</li>
<li>UI Testing이 가장 어렵고 까다로움</li>
<li>Manual Testing은 실행하기 쉽다는 장점이 있지만 비용이 많이 들고 부정확하며 실행 시간이 오래 걸림</li>
<li>자동화 할 수 있지만 UI Testing은 자동화 하기가 가장 까다롭고 또 실행하기도 까다로움</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 59일차 Redux-Toolkit]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-59%EC%9D%BC%EC%B0%A8-Redux-Toolkit</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-59%EC%9D%BC%EC%B0%A8-Redux-Toolkit</guid>
            <pubDate>Fri, 22 Jul 2022 04:49:25 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-59일차">수업 59일차</h2>
<h2 id="이론">이론</h2>
<ul>
<li>ReDux: 앱 전체 상태를 쉽게 관리하기 위한 라이브러리
Action: 상태의 변경을 나타내는 개념
Action Creator: Action을 생성하는 함수
Store: 앱 전체의 상태를 보관하는 곳
Reducer: Action을 받아 새로운 State를 만듦
Dispatch: Action을 redux로 보내는 함수
Selector: 특정 state 조각을 store로부터 가져오는 함수
middleware: action은 Dispatch이후 모든 middleware를 먼저 통과한후에 reducer에 도달
enhancer: action은 Dispatch이후 모든 middleware를 먼저 통과한후에 reducer에 도달
redux-Toolkit: redux에서 공식적으로 추천하는 helper 라이브 러리
configureStore: redux의 createStore 함수를 래핑
createAction: Action creator를 만드는 함수
createReducer: reducer를 만듦
createSlice: Slice는 Action creator, reducer등 별도로 만들어야하는 여러 Redux구현체를 하나의 객체로 모은것
createSeletor: createSeletor 함수를 이용해, state를 이용한 특정 데이터를 리턴하도록 함
react-redux: redux를 react 앱에 연결하게 하는 라이브러리
Provider: Redux store를 React와 연결하기 위해서는 반드시 Provider로 컴포넌트를 감싸야만 함
useDispatch: redux의 dispatch함수를 가져오기위한 API
useSelector: Redux store로부터 데이터를 얻기 위한 API
createAsyncThunk: redux-toolkit에서는 thunk middleware를 디폴트로 추가</li>
</ul>
<h2 id="redux-toolkit">Redux Toolkit</h2>
<p>리덕스 툴킷이란 리덕스를 더 사용하기 쉽게 만들기 위해 리덕스에서 공식 제공하는 개발도구 입니다. 리덕스는 훌륭한 라이브러리지만 단점 역시 있습니다. 리덕스 툴킷은 이러한 리덕스의 단점을 보완하기 위해 등장하였습니다.</p>
<h3 id="리덕스의-단점">리덕스의 단점?</h3>
<ul>
<li>하나의 리덕스를 작성하는데 필요한 기본적인 코드양이 너무 많다(boilerPlate code)</li>
<li>많은 패키지 필요성 (의존성이 높다)</li>
<li>스토어 구성 복잡성</li>
</ul>
<p>우리는 리덕스를 사용하면서 편리함을 느끼지만 한편으로는 너무 눈에 보이는 단점을 가지고 있다고 한번쯤 생각했을 것이다.
이러한 단점과 툴킷을 비교해보며 툴킷을 본격적으로 다루기 전 왜 필요한지 왜 현시점
리덕스를 활용하는 개발에서 툴킷이 필수라는 소리를 들으며 트렌드가 되었는지 알아보겠다.</p>
<h3 id="redux-toolkit이-등장한-이유">Redux Toolkit이 등장한 이유</h3>
<p>리덕스의 단점중에 첫번째로 너무 많은 boiler plate code를 준비해야 한다는 것이다.</p>
<p>액션 타입, 액션 생성함수, 리듀서 이렇게 3가지 종류의 코드를 준비해야한다. 또한 다른 패키지를 사용하지 않는다면 코드가 불필요하게 길어진다(제 리덕스 velog 참고).</p>
<p>물론 적응하게 되겠지만 프로젝트가 커지면서 하나의 리듀서에서 다루는 상태가 커지고 세부적인 업데이트가 많아지면 불변성을 지키기 위해 ...state를 지속적으로 사용하는것도 번거롭습니다.</p>
<p>물론 immer 라이브러리를 활용해 불변성을 지키며 리덕스를 사용하는데 어느정도 도움을 받을 수는 있지만 이런식으로 관련 문제를 해결하기위해 계속해서 다른 패키지를 install받아서 사용한다는 점 또한 다른 패키지 의존성이 높다는 점에서 리덕스의 단점으로 꼽을 수 있습니다.</p>
<p>이뿐만 아니라 불필요한 렌더링을 막기위해 우리는 reselect를 사용해야 했으며 비동기 작업을 위해서는 관련 미들웨어 라이브러리인 thunk나 saga를 사용해야 했습니다.</p>
<p>하지만 redux-toolkit은 위의 문제점에서 saga를 제외한 모든 기능을 제공하여 리덕스의 단점을 보완합니다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/ecdeee09-0c46-4482-b650-0b1f99849dc0/image.png" alt=""></p>
<p>위의 사진은 리덕스 툴킷 공식문서 첫번째 페이지에서 보여주는 리덕스 툴킷의 장점을 간단히 설명한것이다.</p>
<p>실제 아래의 내용을 통해 학습하면 위의 사진에서 보여주는 강점을 직접 체험해 볼 수 있는데 사용전 해당 사진이 포함된 첫번째 장부터 API DOCS에 게시되어있는 내용을 한번 읽어보는 것이 좋을 것 같다.</p>
<h3 id="사용방법">사용방법</h3>
<pre><code>npm install @reduxjs/toolkit</code></pre><h3 id="redux-toolkit-사용해-보기">Redux-Toolkit 사용해 보기</h3>
<p>리덕스로 작성된 코드를 툴킷으로 리팩토링을 진행하다보면 전체적인 코드양이 감소하고 반복되는 코드 또한 제거할 수 있는데 직접 해보면서 리덕스 툴킷을 이해하겠다. 우선 리덕스 툴킷이 없을 때 코드를 보도록하자.</p>
<pre><code class="language-js">export const ADD_TODO = &quot;ADD_TODO&quot;;
export const DELETE_TODO = &quot;DELETE_TODO&quot;;
export const UPDATE_TODO = &quot;UPDATE_TODO&quot;;
export const CHECKED_TODO = &quot;CHECKED_TODO&quot;;

export function addTodo(todo) {
    return{
        type:ADD_TODO,
        payload: todo
    }
}

export function deleteTodo(todo) {
    return{
        type:DELETE_TODO,
        payload: todoId
    }
}

export function updateTodo(todo) {
    return{
        type:UPDATE_TODO,
        payload: todo
    }
}

export function checkedTodo(todo) {
    return{
        type:CHECKED_TODO,
        payload: todo
    }
}

const initialState = [
  {
    id: 0,
    text: &#39;redux&#39;,
    checked: false
  }
];

const initialState = []

export const reducer = (state = initialState, action) =&gt; {
  let newTodos = [...state];

  switch (action.type) {
    case ADD_TODO:
      return newTodos.concat(action.payload)
    case DELETE_TODO:
    newTodos = newTodos.filter((todo) =&gt; todo.id !== action.payload);
     return newTodos;
    case UPDATE_TODO:
      const index = newTodos.findIndex((todo) =&gt; todo.id === action.payload.id);
     newTodos[index] = action.payload;
    return newTodos;
    case CHECKED_TODO:
      const findCheckedIndex = newTodos.findIndex((todo) =&gt; todo.id === action.payload.id);
    newTodos[findCheckedIndex].checked = !newTodos[findCheckedIndex].checked;
     return newTodos;
  }
  return state;
};

export default reducer</code></pre>
<p>위의 코드는 투두 리스트에서 추가, 삭제, 수정, 완료 기능이 있는 리덕스를 덕스 패턴을 활용해 작성한 코드다.</p>
<p>보시면 위에서 우리가 단점이라 하였던 boilerplate code가 너무 많다는걸 한눈에 확인할 수 있다.</p>
<p>물론 여기서 코드를 줄일 수는 있다.</p>
<pre><code>npm install --save redux-actions
npm install immer</code></pre><p>이러한 패키지를 사용해 redux-actions에서 제공하는 createAction, handleAction을 통해 조금더 코드를</p>
<p>간소화 할 수 있고 또다른 문제점인 불변성 해결을 위해 immer라이브러리를 사용할 수 있다.</p>
<p>여기서 우리는 리덕스가 각 목적별로 패키지에 대한 의존성이 높다는 것이다.</p>
<p>패키지 의존성이 높다는 것은 하나의 덕스패턴 구조를 작성할 때 마다 필요로하는 패키지를 Import 해와야한다.</p>
<p>이제 이러한 단점을 모두 보완해 주었다는 리덕스 툴킷을 통해 완성된 덕스패턴 구조를 보도록 하자.</p>
<pre><code class="language-js">import { createAction, createReducer } from &#39;@reduxjs/toolkit&#39;;

export const ADD_TODO_SAGA = &#39;todo/ADD_TODO_SAGA&#39;;
export const DELETE_TODO_SAGA = &#39;todo/DELETE_TODO_SAGA&#39;;
export const UPDATE_TODO_SAGA = &#39;todo/UPDATE_TODO_SAGA&#39;;
export const CHECKED_TODO_SAGA = &#39;todo/CHECKED_TODO_SAGA&#39;;

export const addTodoSaga = createAction(ADD_TODO_SAGA);
export const deleteTodoSaga = createAction(DELETE_TODO_SAGA);
export const updateTodoSaga = createAction(UPDATE_TODO_SAGA);
export const checkedTodoSaga = createAction(CHECKED_TODO_SAGA);

const reducer = createReducer([], {
  [addTodoSaga]: (state, action) =&gt; {
    state.push(action.payload);
  },
  [deleteTodoSaga]: (state, action) =&gt; {
    return state.filter((todo) =&gt; todo.id !== action.payload);
  },
  [updateTodoSaga]: (state, action) =&gt; {
    const index = state.findIndex((todo) =&gt; todo.id === action.payload.id);
    state[index] = action.payload;
  },
  [checkedTodoSaga]: (state, action) =&gt; {
    const index = state.findIndex((todo) =&gt; todo.id === action.payload.id);
    state[index].checked = !state[index].checked;
  }
});
export default reducer;</code></pre>
<h3 id="createaction">createAction</h3>
<p>위의 코드에서 보면 액션 함수를 정의하기위해 2번의 과정이 필요하며 별도 액션 타입을 정의 해 주어야한다.</p>
<p>하지만 변경된 코드에서는 한번의 과정이면 되고 타입만 인자로 넣어주면 default로 타입을 가진 액션 함수를 생성해 주며 이 함수 호출시 파라미터를 추가로 넣어주면 자동으로 payload의 value값으로 들어간다.</p>
<p>왜 이런게 가능할까?</p>
<p>어느정도 예측이 가능한데 createAction은 3가지를 파라미터로 가져온다.</p>
<ol>
<li>액션이름</li>
<li>payloadCreator</li>
<li>metaCreator</li>
</ol>
<p>이러한 이유로 코드는 대폭 감소하면서 전과 같은 기능을 할 수 있는 것이다.</p>
<h3 id="createreducer">createReducer</h3>
<p>위의 코드를 보면 눈에 띄게 리듀서 함수의 코드가 줄은 것을 확인할 수 있다.</p>
<p>또한 initialState를 따로 선언해 주지않았다는것, newTodos를 선언해 상태값을 스프레드 연산자를 통해 얕은 복사를 해서 사용하지 않고 그냥 state값에 직접 접근을 했다는점 또한 concat이라는 메서드를 통해 원본배열 파괴를 막는게 아니라 push라는 원본 배열을 파괴하는 메서드를 사용했다는점이 눈에 들어온다.</p>
<p>하나씩 알아보자.</p>
<p>createReducer를 사용하면 리듀서 함수의 간소화가 가능한데 두가지 인수를 필요로 한다.</p>
<p>첫째로 초기상태(initialState), 두번째로는 액션타입에서 case Reducer로의 객체 매핑이며 각각은 하나의 특정 액션 유형을 처리해 준다.</p>
<p>이러한 이유로 initialState를 따로 지정할 필요도 없으며 switch/case문을 사용할 필요 또한 없게 되었다.</p>
<p>또한 createReducer는 불변성을 지켜주는 것을 더 쉽게 해줍니다.</p>
<p>툴킷을 사용한 리듀서 함수는 내부적으로 immer의 produce를 사용합니다. 그렇기 때문에 새로운 상태 객체를 리턴할 필요가 없습니다. 그대신 상태 값을 직접 변경하는 방식으로 코드를 작성하면 됩니다.</p>
<p>툴킷 createReducer공식문서에서 확인할 수 있듯 createReducer는 자체적으로 불변성을 쉽게 처리하기 위해 immer를 사용하고 이는 마치 상태를 직접변경하는 것처럼 리듀서를 작성할 수 있으며 실제 리듀서는 모든 변형을 동등한 복사 작업으로 변환하는 proxy 상태를 받습니다.</p>
<h3 id="configurestore">configureStore</h3>
<p>우선 configureStore를 사용하기전 store.js를 보자</p>
<pre><code class="language-js">// store.js

import { createStore, applyMiddleware } from &#39;redux&#39;;
import { composeWithDevtools } from &#39;redux-devtools-extension&#39;;
import createSagaMiddleware from &#39;redux-saga&#39;;
import index from &#39;./modules/index&#39;;
import rootSaga from &#39;./sagas&#39;;
import logger from &#39;redux-logger&#39;;

const sagaMiddleware = createSagaMiddleware();
const middleware = applyMiddleware(sagaMiddleware, logger);
const devTools = composeWithDevtools(...middleware);
export const store = createStore(index, devTools);

sagaMiddleware.run(rootSaga);</code></pre>
<p>코드를 보면 리듀서 함수들과 사용된 미들웨어를 합쳐서 하나의 스토어를 구성해 주기 위해 createStore를 사용하고, 미들웨어들을 합쳐주기 위해 applyMiddleware를 사용하고, 리덕스 데브툴을 사용하기 위해 composeWithDevTools를 redux-devtools-extension에서 install 받고 import받아와서 사용하고...</p>
<p>물론 작동한다 하지만 보시다시피 너무 많은 패키지에 대한 의존성이 보인다.</p>
<p>이제 configureStore를 사용해서 변환해 보자.</p>
<pre><code class="language-js">import createSagaMiddleware from &#39;redux-saga&#39;;
import logger from &#39;redux-logger&#39;;
import index from &#39;./modules/index&#39;;
import rootSaga from &#39;./sagas/index&#39;;
import { configureStore } from &#39;@reduxjs/toolkit&#39;;

const sagaMiddleware = createSagaMiddleware();
const middlewares = [logger, sagaMiddleware];

export const store = configureStore({
  devTools: true, //없어도 됩니다.
  middleware: middlewares,
  reducer: index
});

sagaMiddleware.run(rootSaga);</code></pre>
<p>사가를 제외하고는 import부분에 redux/toolkit뿐입니다.</p>
<p>또한 import 아래부분의 코드도 훨씬 간결해진 것을 확인할 수 있습니다.</p>
<p>왜이런게 가능한지 확인해 보겠습니다.</p>
<p>configureStore를 사용하면 5가지를 인자로 받아옵니다.</p>
<p>reducer, middleware, devTools, preloadedState, enhancer를 받아옵니다.</p>
<p>combineReducer에서 전달된 rootReducer를 다루는 reducer,</p>
<p>사용하는 미들웨어를 등록할 수 있는 middleware,</p>
<p>별도의 설치없이 redux-devtools를 사용하게 해주는 devTools 여기서 devTools는 boolean값을 통해 등록</p>
<p>하는데 만약 dev-Tools를 사용한다면 true를 셋팅해줄 필요없이 devTools자체를 삭제해 줘두 된다.</p>
<p>기본값으로 true가 지정되 있기 때문이다.</p>
<p>만약 사용하지 않을 거라면 devTools: false로 셋팅하면 된다.</p>
<p>이러한 이유로 인해 configureStore를 사용하면 사용하는 패키지가 줄어들고 코드또한 간결해 지는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[엘리스 sw 엔지니어 트랙] 58일차 Redux, Jest]]></title>
            <link>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-58%EC%9D%BC%EC%B0%A8-Redux-Jest</link>
            <guid>https://velog.io/@emong_96/%EC%97%98%EB%A6%AC%EC%8A%A4-sw-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4-%ED%8A%B8%EB%9E%99-58%EC%9D%BC%EC%B0%A8-Redux-Jest</guid>
            <pubDate>Fri, 22 Jul 2022 04:39:05 GMT</pubDate>
            <description><![CDATA[<h2 id="수업-58일차">수업 58일차</h2>
<h2 id="이론">이론</h2>
<ul>
<li>Redux: javascript로 구동되는 앱에서 상태관리를 도와주는 라이브러리
Redux Toolkit: Redux를 좀더 쉽게 사용할수 있게 해주는 라이브러리
Jest: Facebook에서 개발한 단위 테스트가 가능한 라이브러리</li>
</ul>
<h2 id="redux">Redux</h2>
<p>리덕스는, 가장 사용률이 높은 상태관리 라이브러리입니다. 리덕스를 사용하면, 여러분이 만들게 될 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리 할 수 있습니다. 또한, 컴포넌트끼리 상태를 공유하게 될 때 여러 컴포넌트를 거치지 않고도 손쉽게 상태 값을 전달 할 수 있습니다.</p>
<p>추가적으로, 리덕스의 미들웨어라는 기능을 통하여 비동기 작업, 로깅 등의 확장적인 작업들을 더욱 쉽게 할 수도 있게 해줍니다. 이 미들웨어에 대해서는 나중에 다뤄보게 됩니다!</p>
<p>필요성 파악하기
리덕스는 글로벌 상태 관리를 하게 될 때 굉장히 효과적입니다. 물론, 리덕스를 사용하는것이 유일한 솔루션은 아닙니다. Context API 를 통해서도 동일한 작업을 할 수 있다는것을 미리 알려드립니다.</p>
<p>먼저, 투두 리스트 앱의 컴포넌트 구조를 살펴보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/fb45c458-9461-47ce-90a8-dad3e2959c7e/image.png" alt=""></p>
<p>투두리스트에서는, CreateForm 에서 우리가 값을 투두 아이템을 추가하면, App 에서 CreateForm 에게 전달해준 handleCreate 함수가 호출되고, 이 함수가 호출 되면 App 의 state 안에 들어있는 todos 값이 업데이트 됩니다.</p>
<p>todos 값이 업데이트 되면, 해당 값이 TodoList 한테 전달되어서 우리가 만든 투두아이템들이 모두 잘 나타나게 되겠죠.</p>
<p>위 구조를 보시면, CreateForm 와, TodoList 간의 데이터 교류를 하기 위해서 App 이라는 부모 컴포넌트가 중간자 역할을 해주었습니다.</p>
<p>위와 같이, 간단한 구조를 갖추고 있는 프로젝트는 글로벌 상태 관리를 위하여 따로 상태 관리 라이브러리를 사용하실 필요가 없습니다.</p>
<p>하지만, 컴포넌트 구조가 조금만 더 복잡해지면 어떨까요?</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/25ca894a-1002-4524-ba27-e45566d779ff/image.png" alt=""></p>
<p>Root 컴포넌트에는 something 이라는 상태 값이 있고, onDoSomething 이라는 함수가 something 값에 변화를 줍니다.</p>
<p>onDoSomething 은 Root -&gt; B -&gt; H 로 전달되고, H 에서 이벤트가 발생하여 이 함수가 호출되면 something 이 Root -&gt; A -&gt; E -&gt; F 로 전달됩니다.</p>
<p>props 가 필요한 곳으로 제대로 전달되게 하기 위하여, 실제로는 해당 props 를 사용하지 않는 컴포넌트를 거쳐가야 한다는 것은 리렌더링 하게 될 때 비효율적이기도하고, 굉장히 귀찮은 작업이기도 합니다. 상위 컴포넌트에서 props 이름을 바꿔준다면 그 아래에도 쭉 바꿔줘야 하니까요.</p>
<p>리덕스가 있다면, 다음과 같은 구조로 작업을 진행 할 수 있게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/emong_96/post/653d17d3-0607-4140-87f7-1fea2da32735/image.png" alt=""></p>
<p>앱이 지니고 있는 상태와, 상태 변화 로직이 들어있는 스토어를 통하여, 우리가 원하는 컴포넌트에 원하는 상태값과 함수를 직접 주입해줄 수 있게 됩니다.</p>
<p>이런 식으로, 더 쉬운 글로벌 상태 관리를 위하여 리덕스를 사용하기도 하고, 조금 더 체계적이고 편리한 상태 관리를 하기 위하여 사용을 하는데, 후자의 경우엔 실제로 사용을 해봐야 경험을 해볼 수 있을 것입니다.</p>
<h3 id="action">Action</h3>
<p>상태에 어떠한 변화가 필요하게 될 땐, 우리는 액션이란 것을 발생시킵니다. 이는, 하나의 객체로 표현되는데요, 액션 객체는 다음과 같은 형식으로 이뤄져있습니다.</p>
<pre><code class="language-js">{
  type: &quot;TOGGLE_VALUE&quot;
}</code></pre>
<p>액션 객체는 type 필드를 필수적으로 가지고 있어야하고 그 외의 값들은 개발자 마음대로 넣어줄 수 있습니다.</p>
<pre><code class="language-js">{
  type: &quot;ADD_TODO&quot;,
  data: {
    id: 0,
    text: &quot;리덕스 배우기&quot;
  }
}</code></pre>
<h3 id="액션-생성함수action-creator">액션 생성함수(Action Creator)</h3>
<p>액션 생성함수는, 액션을 만드는 함수입니다. 단순히 파라미터를 받아와서 액션 객체 형태로 만들어주죠.</p>
<pre><code class="language-js">function addTodo(data) {
  return {
    type: &quot;ADD_TODO&quot;,
    data
  };
}

// 화살표 함수로도 만들 수 있습니다.
const changeInput = text =&gt; ({ 
  type: &quot;CHANGE_INPUT&quot;,
  text
});</code></pre>
<h3 id="리듀서-reducer">리듀서 (Reducer)</h3>
<p>리듀서는 변화를 일으키는 함수입니다. 리듀서는 두가지의 파라미터를 받아옵니다.</p>
<pre><code class="language-js">function reducer(state, action) {
  // 상태 업데이트 로직
  return alteredState;
}</code></pre>
<p>리듀서는, 현재의 상태와, 전달 받은 액션을 참고하여 새로운 상태를 만들어서 반환합니다. 자세한건, 추후 직접 구현하면서 알아보겠습니다.</p>
<h3 id="스토어store">스토어(Store)</h3>
<p>리덕스에서는 한 애플리케이션 당 하나의 스토어를 만들게 됩니다. 스토어 안에는, 현재의 앱 상태와, 리듀서가 들어가있고, 추가적으로 몇가지 내장 함수들이 있습니다.</p>
<h3 id="디스패치dispatch">디스패치(dispatch)</h3>
<p>디스패치는 스토어의 내장함수 중 하나입니다. 디스패치는, 액션을 발생 시키는 것 이라고 이해하시면 됩니다. dispatch 라는 함수에는 액션을 파라미터로 전달합니다.. dispatch(action) 이런식으로 말이죠.</p>
<p>그렇게 호출을 하면, 스토어는 리듀서 함수를 실행시켜서 해당 액션을 처리하는 로직이 있다면 액션을 참고하여 새로운 상태를 만들어줍니다.</p>
<h3 id="구독subscribe">구독(subscribe)</h3>
<p>구독 또한 스토어의 내장함수 중 하나입니다. subscribe 함수는, 함수 형태의 값을 파라미터로 받아옵니다. subscribe 함수에 특정 함수를 전달해주면, 액션이 디스패치 되었을 때 마다 전달해준 함수가 호출됩니다.</p>
<h2 id="jest">Jest</h2>
<p>페이스북에서 만들어서 React와 더불어 많은 자바스크립트 개발자들로 부터 좋은 반응을 얻고 있는 테스팅 라이브러리이다.</p>
<p>Jest 이전에는 자바스크립트 코드를 테스트하라면 여러가지 테스팅 라이브러리를 조합해서 사용했다!</p>
<p>예를 들어, Mocha나 Jasmin을 Test Runner로 사용하고, Chai나 Expect와 같은 Test Mathcher를 사용했으며, 또한 Sinon과 Testdouble 같은 Test Mock 라이브러리도 필요했었다.</p>
<p>이 라이브러리들은 굉장히 유사하지만 살짝씩 다른 API를 가지고 있었기 때문에, 여러 프로젝트에 걸쳐서 일하는 자바스크립트 개발자들에게 혼란을 주기도 했었다.</p>
<p>하지만 Jest는 라이브러리 하나만 설치하면, Test Runner와 Test Mathcher 그리고 Test Mock 프레임워크까지 제공해주기 때문에 현재 대세라고 말할 수 있다.</p>
<h3 id="commonjs---es6">commonJS -&gt; ES6</h3>
<ul>
<li>sum.js<pre><code class="language-js">function sum(a, b) {
return a + b;
}
export default sum;
</code></pre>
</li>
</ul>
<pre><code>
- sum.test.js
```js
import sum from &quot;./sum&quot;;

test(&quot;adds 1 + 2 to equal 3&quot;, () =&gt; {
  expect(sum(1, 2)).toBe(8);
});</code></pre><blockquote>
<p>jest는 CommonJS 모듈 시스템을 사용하기 때문에 ES6 문법을 사용하면 에러가 나타난다!</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>