<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>rokwon_k.log</title>
        <link>https://velog.io/</link>
        <description>Life Designer</description>
        <lastBuildDate>Sun, 01 Jan 2023 16:04:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>rokwon_k.log</title>
            <url>https://velog.velcdn.com/images/rokwon_k/profile/be8eb092-6f4a-44f1-94f0-2f6a0accbcd3/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. rokwon_k.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/rokwon_k" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Terraform - 키워드(provider, resource, data, variable, local)]]></title>
            <link>https://velog.io/@rokwon_k/Terraform-%ED%82%A4%EC%9B%8C%EB%93%9Cprovider-resource-data-variable-local</link>
            <guid>https://velog.io/@rokwon_k/Terraform-%ED%82%A4%EC%9B%8C%EB%93%9Cprovider-resource-data-variable-local</guid>
            <pubDate>Sun, 01 Jan 2023 16:04:55 GMT</pubDate>
            <description><![CDATA[<p>Terrform은 HCL(Hashicorp Configuration Language)라는 설정언어를 사용한다.<br>그에 따라 내부적으로 사용하는 여러 키워드들이 존재하는데 핵심적인 키워드들을 정리해보았다.  </p>
<br />  

<h2 id="계정-서비스와-관련된-키워드">계정, 서비스와 관련된 키워드</h2>
<p>Terraform을 사용하는데 거의 필수적인 키워드로는 <code>terraform</code>, <code>provider</code>, <code>resource</code>, <code>data</code>가 있다. 작은 인프라를 구성하더라도 반드시 필요한 키워드들이다.  </p>
<br />  

<h3 id="terraform">terraform</h3>
<p><code>terraform</code> 키워드로 선언된 블럭은 테라폼 자체에 대한 설정을 작성하는 블럭이다.<br>예를 들어 <code>provider</code>에 대한 버전, tfstate 파일의 원격저장소 설정 등을 이 블럭에 작성한다. 아래의 예시는 provider 중 사용할 aws의 버전을 구체적으로 명시한다.<br><a href="https://developer.hashicorp.com/terraform/language/settings">Terraform Settings 공식문서</a>  </p>
<pre><code class="language-hcl">terraform {
  required_providers {
    aws = {
      source  = &quot;hashicorp/aws&quot;
      version = &quot;4.33.0&quot;
    }
  }
}</code></pre>
<br />  

<h3 id="provider">provider</h3>
<p><strong>Terraform과 외부 서비스를 연결해주는 기능을 한다.</strong><br>알다시피 Terraform은 크로스플랫폼 IaC도구이다. AWS뿐만 아니라 Azure, Google Cloud Platform 등 여러 클라우드 서비스와 연결하여 사용할 수 있다.<br>그에 따라 Terraform은 사용할 provider를 반드시 선언해야한다. 다수의 클라우드 서비스를 이용할 경우 여러개의 provider를 선언할 수 있다. 이후에 살펴볼 <code>resource</code>나 <code>data</code>에서는 각각 선언된 provider 중 선택하여 설정할 수 있다.<br>아래의 링크에서 사용가능한 클라우드 서비스를 확인할 수 있다.
<a href="https://registry.terraform.io/browse/providers">Terraform Providers</a>  </p>
<p>아래와 같이 <code>CLOUDNAME</code> 공간에 사용할 provider를 작성한다.  </p>
<pre><code class="language-hcl">provider CLOUDNAME {
  ...
}</code></pre>
<p>aws를 이용하고자 한다면 아래와 같이 작성할 수 있다.<br>연결할 region과 연결될 IAM User를 작성해 주었다.<br>이 외에도 여러가지 Key를 작성할 수 있다. 첨부한 링크(공식문서)에서 더 알아볼 수 있다.</p>
<pre><code class="language-hcl">provider &quot;aws&quot; {
  region                   = &quot;ap-northeast-2&quot;
  shared_config_files      = [&quot;~/.aws/config&quot;]
  shared_credentials_files = [&quot;~/.aws/credentials&quot;]
  profile                  = &quot;thepool&quot;
}</code></pre>
<p><a href="https://developer.hashicorp.com/terraform/language/providers/configuration">Terraform Provider Configuration Docs</a><br><a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs">Terraform Provider API Docs</a></p>
<br />  

<h3 id="resource">resource</h3>
<p><code>resource</code>는 Terraform에서 가장 중요하며 가장 자주사용하는 요소이다.<br>하나의 <code>resource</code> 블럭은 하나의 인프라 구성요소와 같다. ec2나 subnet 혹은 s3 등 하나의 <code>resource</code>로 표현되어진다.  </p>
<p><code>resource</code>는 다음과 같은 형식으로 작성한다. <code>TYPE</code>은 Terraform에 정의되어 있는 타입의 이름이고 <code>NAME</code>은 개발자가 붙이는 이름이다.  </p>
<pre><code class="language-hcl">resource TYPE NAME {
  ...
}</code></pre>
<p>예를 들어, aws의 vpc를 만들고자 한다면 아래와 같이 작성할 수 있다.<br><code>cidr_bloc</code>이나 <code>tags</code>와 같은 Key들은 aws_vpc를 만들때 필수적이거나 선택적인 설정항목이다.   </p>
<pre><code class="language-hcl">resource &quot;aws_vpc&quot; &quot;my_vpc&quot; {
  cidr_block = &quot;10.0.0.0/16&quot;
  tags = {
    Name = &quot;the-pool-vpc&quot;
  }
}</code></pre>
<p>aws의 여러 resource가 정리된 공식문서이다.
<a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs">Terraform API Docs</a><br><a href="https://developer.hashicorp.com/terraform/language/resources/syntax">Terraform Resource Configuration Docs</a>  </p>
<br />  

<h3 id="data">data</h3>
<p>Terraform 외부에 정의되어 있는 정보를 가져와서 사용할 수 있게 해주는 키워드이다.<br>각 <code>provider</code>들은 <code>resource</code> 뿐만 아니라 <code>data</code>와 관련된 서비스들도 지원해준다.  </p>
<p><code>resource</code>와 마찬가지로 <code>TYPE</code>은 Terraform에 정의되어 있는 타입의 이름이고 <code>NAME</code>은 개발자가 붙이는 이름이다.</p>
<pre><code class="language-hcl">data TYPE NAME {
  ...
}</code></pre>
<p>다음은 aws IAM Policy를 가져와 사용할 수 있도록 하는 코드이다.  </p>
<pre><code class="language-hcl">data &quot;aws_iam_policy_document&quot; &quot;s3_policy&quot; {
  statement {
    principals {
      type        = &quot;*&quot;
      identifiers = [&quot;*&quot;]
    }

    actions = [
      &quot;s3:*&quot;,
    ]

    effect = &quot;Allow&quot;

    resources = [
      &quot;${aws_s3_bucket.s3.arn}&quot;,
    ]
  }
}</code></pre>
<p><a href="https://developer.hashicorp.com/terraform/language/data-sources">Terraform Data Source Configuration Docs</a></p>
<p><br /><hr><br /></p>
<h2 id="변수-출력과-관련된-키워드">변수, 출력과 관련된 키워드</h2>
<p>Terraform으로 인프라를 구축하다보면 필연적으로 코드, 값이 중복되는 일이 허다하다.<br>이를 위해 Terraform에서는 변수 및 결과를 출력해주는 방법을 마련해주었다.<br>아래에서 설명하는 <code>local</code>, <code>var</code>, <code>output</code> 이외에도 모듈화에 필요한 <code>module</code> 키워드도 있지만 매우 중요한 개념이기에 따로 해당 키워드는 따로 정리하였다.  </p>
<br />  

<h3 id="variable">variable</h3>
<p><code>variable</code>을 사용하면 여러 파일에서 같은 모듈을 공유할 수 있어 재사용성이 매우 뛰어나다. 기본값을 설정할 수 있으면 <strong>개발자로 부터 입력을 받을 수 있는 변수</strong>이다.<br>뿐만 아니라 입력값에 조건을 달 수도 있다.  </p>
<p>다음과 같은 구조를 가지며 <code>NAME</code>은 변수의 이름이다. 내부적으로 들어가는 type은 변수의 타입이며 default는 기본설정 값이다.
type으로는 string, number, bool 뿐만아니라, list(TYPE), set, map, object, tuple 이 들어갈 수 있다.  </p>
<pre><code class="language-hcl">variable NAME {
  type = TYPE
  defalut = VALUE
}</code></pre>
<p>입력변수를 사용할때는 <code>var</code>이라는 키워드를 이용하여 접근할 수 있다.  </p>
<pre><code class="language-hcl">variable &quot;vpc_name&quot; {
  type    = string
  default = &quot;test-vpc&quot;
}

resource &quot;aws_vpc&quot; &quot;the_pool_vpc&quot; {
  cidr_block = &quot;10.0.0.0/16&quot;
  tags = {
    Name = var.vpc_name
  }
}</code></pre>
<p>이렇게 선언된 변수는 선언된 모듈내에서만 액세스가 가능하다.<br><code>variables</code>를 입력하는 방법은 여러가지가 있다. CLI로 직접 입력하는 방법, <code>.tfvars</code>내에 작성하고 사용하는 방법, 환경변수로 지정 후 사용하는 방법 등이다.<br>자세한 내용은 아래 공식문서에서 확인할 수 있다.  </p>
<p><a href="https://developer.hashicorp.com/terraform/language/values/variables">Terraform Input Variables Docs</a>  </p>
<br />  

<h3 id="locals">locals</h3>
<p><code>locals</code>은 지역변수 선언 키워드이다. 현재 파일에서만 사용이 가능하다. 특정값들을 연산(merge, concat, max)하여 하나의 변수로 만들때 주로 사용한다.  </p>
<p><code>NAME</code>에 변수명이 들어가고 <code>VALUE</code>에는 값이 들어간다.  </p>
<pre><code class="language-hcl">locals {
  NAME = VALUE
}</code></pre>
<p><code>locals</code>에 정의된 값은 <code>local.NAME</code>으로 사용가능하다.  </p>
<pre><code class="language-hcl">locals {
  # Common tags to be assigned to all resources
  common_tags = {
    Service = local.service_name
    Owner   = local.owner
  }
}

resource &quot;aws_instance&quot; &quot;example&quot; {
  # ...
  tags = local.common_tags
}</code></pre>
<p><a href="https://developer.hashicorp.com/terraform/language/values/locals">Terraform Local Values Docs</a></p>
<br />  

<h3 id="output">output</h3>
<p><code>output</code>은 인프라에 대한 정보를 명령줄에 노출(숨기기 가능)시킬 수 있으며 자식 모듈의  <code>output</code>값에 접근하여 사용할 수 있다.  </p>
<p><code>NAME</code>에는 지정할 이름이 들어가고 <code>VALUE</code>에는 출력할 결과값을 작성할 수 있다.  </p>
<pre><code class="language-hcl">output NAME {
  value = VALUE
} </code></pre>
<p>다음은 생성된 ECR <code>resource</code>의 url을 출력한다.  </p>
<pre><code class="language-hcl">resource &quot;aws_ecr_repository&quot; &quot;ecr&quot; {
  name                 = &quot;test_ecr&quot;
  image_tag_mutability = &quot;MUTABLE&quot;
  image_scanning_configuration {
    scan_on_push = true
  }
}

output &quot;ecr_registry_url&quot; {
  value = aws_ecr_repository.ecr.repository_url
}</code></pre>
<p><a href="https://developer.hashicorp.com/terraform/language/values/outputs">Terraform Output Values Docs</a>  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform - 기본 명령어, 파일/디렉터리]]></title>
            <link>https://velog.io/@rokwon_k/Terraform-%EA%B8%B0%EB%B3%B8-%EB%AA%85%EB%A0%B9%EC%96%B4-%ED%8C%8C%EC%9D%BC%EB%94%94%EB%A0%89%ED%84%B0%EB%A6%AC</link>
            <guid>https://velog.io/@rokwon_k/Terraform-%EA%B8%B0%EB%B3%B8-%EB%AA%85%EB%A0%B9%EC%96%B4-%ED%8C%8C%EC%9D%BC%EB%94%94%EB%A0%89%ED%84%B0%EB%A6%AC</guid>
            <pubDate>Sun, 01 Jan 2023 10:15:24 GMT</pubDate>
            <description><![CDATA[<p>간단하게 Terraform을 배우고 프로젝트에 바로 도입하였다가 프로젝트의 복잡도가 높아지면서 제대로 익히지 않았던 부분들이 발목을 잡았다. 한 번 얻어맞고보니 개념정리가 필요하다 싶어 정리해보았다.  </p>
<br />  

<h3 id="기본명령어---init-plan-apply">기본명령어 - init, plan, apply</h3>
<p><code>terraform init</code> provider가 정의되어 있는 위치에서 해당 명령어를 입력한다면 provider, module, state를 설정한다.(사용준비)<br>👉 .terraform 디렉터리 생성<br>👉 .terraform.lock.hcl 파일 생성  </p>
<p><code>terraform plan</code> 이전 state와 비교하여 변경될 내역을 보여준다.  </p>
<p><code>terraform apply</code> 현재 작성된 코드를 기준으로 인프라에 적용한다.<br>👉 terraform.tfstate 파일 생성/업데이트
👉 .terraform 디렉터리 업데이트
👉 .terraform.lock.hcl 파일 업데이트</p>
<p><br /><br /></p>
<h2 id="terrform-파일디렉터리">Terrform 파일/디렉터리</h2>
<p>명령어를 사용하면 여러 파일/디렉터리가 생성, 업데이트가 되는 것을 볼 수 있다.<br>이렇게 생성된 파일/디렉터리 들은 어떤 역할을 하는 것일까?  </p>
<br />  

<h3 id="terraform-디렉터리">.terraform 디렉터리</h3>
<p>의존되는 파일들이 다운로드(외부, 로컬까지도) 되어 저장되는 공간이다.<br>내부적으로 .tfstate의 정보 또한 포함된다.  </p>
<br />  

<h3 id="terraformlockhcl">.terraform.lock.hcl</h3>
<p><strong>의존성 관련 잠금파일이다.</strong> 의존되는 파일들에 대해 명시되어 있다.<br>깃과 같은 버전관리 시스템에 <code>.terrform 디렉터리</code>를 업로드하지 않고 이 파일만 업로드한다. 이후 다른 PC/유저들은 <code>init</code> 명령어를 이용해서 이 파일에 명시되어 있는 의존 파일들을 다운로드 한다.  </p>
<br />  

<h3 id="terraformtfstate">terraform.tfstate</h3>
<p>Terrform의 상태파일로 <code>apply</code>를 통해 실제 인프라에 적용한 결과를 기억하는 파일이다.<br>따로 설저하지 않는이상 로컬에서 관리되지만 인프라를 설계하는 인원이 많아질수록 이 파일을 원격으로 공유하면서 사용해야 할 이유가 명확하다.(각 로컬에서 관리할경우 모든 이들의 tfstate가 다르므로 인프라가 꼬일 확률이 높아진다.)<br>또한 실제 인프라와 <code>.tfstate 파일</code>을 일치시키는 것이 중요하다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS IAM 서비스]]></title>
            <link>https://velog.io/@rokwon_k/AWS-IAM-%EC%84%9C%EB%B9%84%EC%8A%A4</link>
            <guid>https://velog.io/@rokwon_k/AWS-IAM-%EC%84%9C%EB%B9%84%EC%8A%A4</guid>
            <pubDate>Mon, 26 Dec 2022 08:21:16 GMT</pubDate>
            <description><![CDATA[<p>AWS를 처음 사용할때 콘솔에서 계정/비밀번호를 이용하여 로그인한다. 일반적인 서비스에서의 로그인방법과 유사하다.<br>하지만 AWS는 인프라를 구성할 수 있는 서비스이다. 조직에서 인프라에 접근할 수 있는 사람은 한 명이 아니다. 그렇다면 다수의 인원이 하나의 계정을 사용할까? 또는 막 합류한 개발자에게 기존 서비스를 삭제하고 생성할 수 있는 권한이 있다면 문제가 생기지 않을까? 이러한 <strong>보안문제에 대한 해답을 제시하는 서비스가 바로 AWS IAM</strong>이다.  </p>
<br />

<blockquote>
<p><strong>💡 IAM이란?</strong><br>Identity and Access Management의 약자.<br>사용자/그룹을 생성할 수 있으며 리소스에 대한 접근권한을 부여할 수 있다.<br>여러 사용자에게 AWS 계정에 대한 접근을 허용할 수 있으며 내/외부적인 보안문제를 처리할 수 있다.</p>
</blockquote>
<p>아래의 그림은 하나의 계정에 대해 IAM을 사용하여 사용자들을 어떤식으로 구성할 수 있는지를 보여준다.</p>
<p><img src="https://user-images.githubusercontent.com/52196792/209523402-82eeb909-f566-4096-8932-bd5361cecb3a.png" alt="IAM"></p>
<br />

<h3 id="iam을-사용하는-이유">IAM을 사용하는 이유?</h3>
<p>위에서 이미 얘기했지만 더 자세히 살펴보자</p>
<ul>
<li><strong>AWS 계정을 사용하도록 허용하기 위해서</strong><ul>
<li>하나의 계정을 여러 사용자들이 사용할 수 있음</li>
<li>사용자/그룹 별로 정책을 이용하여 접근권한 수준을 정할 수 있음</li>
</ul>
</li>
<li><strong>내/외부적인 보안문제 해결</strong><ul>
<li>정책을 이용하여 사용자별 권한수준 정의(AWS는 <strong>최소 권한 원칙</strong>준수를 지향한다.)</li>
<li>IAM에서 제공하는 보안기술들을 이용하여 해킹의 위험성 낮출 수 있음</li>
</ul>
</li>
</ul>
<p><br /><hr><br /></p>
<h2 id="iam의-기능">IAM의 기능</h2>
<p>IAM에는 여러 기능이 존재한다.<br>IAM에 존재하는 기능들은 유저/그룹/리소스 에게 권한을 설정, 계정/유저 단위의 보안과 연관된 것들이다.<br>AWS를 운용하기 위해 필요한 기능들을 살펴보자  </p>
<br />

<h3 id="iam-policy">IAM Policy</h3>
<p>IAM Policy(정책)은 <strong><code>권한에 대한 명세</code></strong> 라고 볼 수 있다.<br><strong>유저/그룹/리소스 들은 이 정책을 부여받아 특정 리소스에 대한 접근권한</strong>을 가질 수 있다.  </p>
<p>정책은 JSON 문서이며 문서 내 작성된 내용에 따라 접근수준이 정해진다. 각 유저/그룹은 여러개의 정책을 가질 수 있다.  </p>
<p>JSON 문서 구조를 살펴보자면 </p>
<table>
<thead>
<tr>
<th align="left">Key</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>Version</code></td>
<td align="left">문서 버전명. 보통 &#39;2012-10-17&#39;</td>
</tr>
<tr>
<td align="left"><code>ID</code></td>
<td align="left">(Optional) 정책에 대한 ID</td>
</tr>
<tr>
<td align="left"><code>Statement</code></td>
<td align="left">실제 권한에 대한 정보가 들어감</td>
</tr>
</tbody></table>
<p><code>Statement</code>내에 들어가는 값들 또한 형식이 정해져 있다.</p>
<table>
<thead>
<tr>
<th align="left">Key</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>Sid</code></td>
<td align="left">(Optional) 해당 문장의 ID로 문장의 식별자</td>
</tr>
<tr>
<td align="left"><code>Effect</code></td>
<td align="left">해당 정책을 Allow 할 것인지 Deny 할것인지를 정의</td>
</tr>
<tr>
<td align="left"><code>Principal</code></td>
<td align="left">정책이 적용될 사용자, 계정 혹은 역할로 구성된다</td>
</tr>
<tr>
<td align="left"><code>Action</code></td>
<td align="left">Effect에 기반하여 허용/거부 되는 API 호출 목록</td>
</tr>
<tr>
<td align="left"><code>Resource</code></td>
<td align="left">적용될 Action의 리소스의 목록</td>
</tr>
<tr>
<td align="left"><code>Condition</code></td>
<td align="left">(optional) 이 정책이 언제 적용될지를 결정함</td>
</tr>
</tbody></table>
<br />

<p>실제 정책에 대한 예시를 보자</p>
<pre><code class="language-json">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Id&quot;: &quot;S3-Permissions&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;1&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;AWS&quot;: [&quot;arn:aws:iam::1234566789012:root&quot;]
      },
      &quot;Action&quot;: [
        &quot;s3:GetObject&quot;,
        &quot;s3:PutObject&quot;
      ],
      &quot;Resource&quot;: [&quot;arn:aws:s3:::testbucket/*&quot;]
    }
  ]
}</code></pre>
<p>위 코드에 대한 설명은 아래와 같다  </p>
<pre><code class="language-txt">Principal : root 사용자에게  
Resource  : testbucket 이름을 가진 s3 서비스의 모든경로에 대해
Action    : Get과, Put을 할수 있는 권한을  
Effect    : 허용하겠다</code></pre>
<p>위와 같은 형식을 이용하여 권한에 대한 명세. 즉, 정책을 정의할 수 있다.</p>
<br />  

<h3 id="iam-usergroup">IAM User/Group</h3>
<p>IAM User(사용자)은 실제로 단 한 명의 사용자를 의미하는 것이다.<br>IAM Group은 다수의 IAM 사용자를 포함할 수 있다. 그룹을 이용하여 정책을 다수의 사용자에게 매핑 시킬 수 있다. </p>
<p><img src="https://user-images.githubusercontent.com/52196792/209518566-f5285671-8f97-4713-ace5-c8a4dd50d93c.png" alt="IAM User">  </p>
<p><img src="https://user-images.githubusercontent.com/52196792/209518730-b70f783c-8e20-43da-a117-f6f7a3ac5252.png" alt="IAM User">  </p>
<br />

<h3 id="iam-role">IAM Role</h3>
<p>IAM Role(역할)은 특정 개체(User, AWS 서비스, 다른 계정 등)로의 접근권한을 가진다.<br>Role은 Policy을 가진다는 것에서 IAM User와 비슷하게 보이지만 영원히 가지고 있는 권한이 아닌 <code>임시 보안 자격 증명</code>이다.<br>보통 Role은 모자로 비유가 된다. 언제든 썼다 벗을 수 있다는 것이다.<br>또한 Role은 사용자뿐만 아니라 서비스에게도 부여할 수 있다.(AWS 내 서비스에게 권한 부여한다는 뜻)  </p>
<p>임시 자격 증명이기때문에 보다 안전하다는 장점이 있다.</p>
<p><img src="https://user-images.githubusercontent.com/52196792/209520973-0a0e3f01-9779-4a08-b050-6ad2c029e69e.png" alt="IAM User">  </p>
<br />  

<h3 id="iam-보안password-mfa">IAM 보안(Password, MFA)</h3>
<p>사용자의 정보가 침해당하지 않도록 보호하기 위해 두가지 방어 메커니즘이 있다.  </p>
<ul>
<li>비밀번호 정책 - 비밀번호가 강할수록 보안이 높음<ul>
<li>최소 글자 수, 특정 문자들을 요구</li>
<li>유저 비밀번호 변경을 허용 또는 금지</li>
<li>일정 기간지나면 비밀번호 만료시키기(이전 비밀번호와 동일하게 변경 불가능)</li>
<li>AWS IAM -&gt; 좌측 메뉴중 Account settings에서 비밀번호 정책 확인가능  </li>
</ul>
</li>
<li>MFA(Multi Factor Authentication)<ul>
<li>MFA = 비밀번호와 보안장치를 함께 이용하는 것  (더 안전해진다)</li>
<li>해킹당해 비밀번호가 누출되어도 보안(물리적)장치가 필요하므로 계정에 침투 불가능</li>
<li><code>가상 MFA 장치</code> = Google Authenticator(phone only), Authy(multi device)</li>
<li><code>Universal 2nd Factor(U2F) Security Key</code> = 물리적 장치임 YubiKey(하나의 키로 여러 유저의 정보를 가질 수 있음)</li>
<li><code>Hardware Key Fob MFA Device</code> = Gemalto 회사의 제품, AWS GovCloud(미국 정부가 제공)</li>
<li>상단 계정클릭 -&gt; Security Credential -&gt; MFA 확성화</li>
</ul>
</li>
</ul>
<br />

<h3 id="iam-security-tools">IAM Security Tools</h3>
<ul>
<li>IAM Credentials Report(계정 레벨에서 확인)  <ul>
<li>계정을 언제 사용했는지, 비밀번호를 언제 변경했는지 등</li>
</ul>
</li>
<li>Access Advisor(유저 레벨에서 확인)  <ul>
<li>액세스 로깅을 확인하고 해당 유저는 이정도의 권한만 있으면 되겠다 판별할 수 있음</li>
<li>이를 이용해 최소 권한 원칙을 적용할 수 있음</li>
</ul>
</li>
</ul>
<p><img src="https://user-images.githubusercontent.com/52196792/209522230-7dd184d4-4bf1-4713-8f5f-7366205d85ba.png" alt="IAM Credentials Report">  </p>
<p><img src="https://user-images.githubusercontent.com/52196792/209522463-c48e235a-cb21-4b16-9978-bffafa31bb13.png" alt="Access Advisor"></p>
<br />  

<h3 id="iam-제대로-사용하기">IAM 제대로 사용하기</h3>
<ul>
<li>루트 계정으로는 접근하지 말자(필요하다면 MFA를 이용하여 보안수준을 높인 후 이용하자)</li>
<li>하나의 유저는 한 명의 실제 사용자를 의미하도록 하자</li>
<li>그룹을 이용하여 그룹 수준에서 보안을 관리할 수 있도록 하자</li>
<li>비밀번호 정책을 사용하자</li>
</ul>
<br />

<h3 id="iam-키워드-요약">IAM 키워드 요약</h3>
<ul>
<li>Users : 실제 사용자와 IAM 사용자를 매핑</li>
<li>Groups : 사용자만을 포함한다.(다른 그룹을 포함할 수는 없다)</li>
<li>Policies : JSON 문서로 만들어진 권한 정책으로 유저나 그룹에게 부여할 수 있다.(접근 권한에 대한 명세)</li>
<li>Roles : 사용자 및 AWS 서비스에게 다른 서비스로의 임시 접근 권한을 부여할 수 있다. </li>
<li>Security : MFA + Password Policy</li>
<li>Security Tools : IAM Credential Reports &amp; IAM Access Advisor</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CloudFront 살펴보기]]></title>
            <link>https://velog.io/@rokwon_k/CloudFront-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@rokwon_k/CloudFront-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 17 Dec 2022 14:13:22 GMT</pubDate>
            <description><![CDATA[<p>짧은 지연시간과 빠른 속도로 데이터, 동영상, 애플리케이션 및 API를 전송하는 고속 콘텐츠 전송 네트워크(CDN)  </p>
<blockquote>
<p><strong>💡 CDN(Content Delivery Network)</strong>
페이지, 이미지, 동영상 서버에서 받아와 캐싱<br>빠른 컨텐츠 제공<br>서버로 요청이 필요 없기 때문에 서버의 부하를 낮추는 효과  </p>
</blockquote>
<br />  

<h3 id="동작-방식">동작 방식</h3>
<p><img src="https://user-images.githubusercontent.com/52196792/208246079-36fed2ca-524c-4940-a0d7-f239b85ebc53.png" alt="cloudfront"></p>
<ol>
<li>클라이언트로부터 요청 받은 컨텐츠가 엣지 로케이션에 있다면 전달  </li>
<li>요청 받은 컨텐츠가 엣지 로케이션에 없다면 <code>Origin</code> 으로부터 제공받아 전달(이때 캐싱)  </li>
</ol>
<blockquote>
<p><strong>💡 엣지 로케이션</strong><br>컨텐츠가 캐싱되고 유저에게 제공되는 지점.<br>즉, cloudFront가 물리적으로 존재하는 공간  </p>
</blockquote>
<br />  

<h3 id="cloudfront의-구성">CloudFront의 구성</h3>
<p><strong><code>Origin</code></strong><br>CloudFront의 실제 컨텐츠가 존재하는 근원 리소스이다.</p>
<ol>
<li>S3<ul>
<li>파일을 배포하고 엣지에서 캐시할때 자주 사용</li>
<li>OAI(Origin Access Identity)를 이용하여 S3와 CloudFront 사이의 보안강화(S3가 오직 CloudFront하고만 소통할 수 있도록 해줌)</li>
<li>엣지 로케이션이 S3에 접근하기 위해선 IAM 역할인 OAI 신분을 사용해야 함, 버킷 정책이 이 역할의 액세스를 허용해야함</li>
<li>CloudFront가 S3로 접근하기 위한 입구로 사용될 수 있음</li>
</ul>
</li>
<li>Custom Origin(HTTP)<ul>
<li>HTTP 접근이 가능한 리소스(ELB, S3에 업로드된 웹사이트, EC2)들의 Origin이 될 수 있음</li>
<li>ELB나 EC2의 경우 엣지로케이션이 접근하기 위해선 Public해야하며 엣지 로케이션의 IP를 허옹해야함</li>
<li>CloudFront가 HTTP로 접근하기 위한 입구로 사용될 수 있음</li>
</ul>
</li>
</ol>
<br />  

<p><strong><code>Distribution</code></strong>  </p>
<ul>
<li>CloudFront의 CDN 구분 단위</li>
<li>여러 엣지 로케이션으로 구성된 컨텐츠 제공 채널  </li>
</ul>
<br />  

<p><strong><code>주요 설정 및 용어</code></strong></p>
<ul>
<li>TTL(Time To Live) : 캐싱된 아이템이 살아있는 시간</li>
<li>파일 무효화(invalidate) : TTL이 지나기 전에 강제로 캐시 삭제</li>
<li>OAI(Origin Access Identity) : CloudFront가 근원 리소스에 접근하기 위한 신분증(IAM 대신), 각 정책은 이 신분증에 대해 접근을 허용해야함.</li>
</ul>
<br />  

<h3 id="장점">장점</h3>
<ul>
<li>DDos 공격을 방어하는 방화벽을 제공해줌</li>
<li>인증서를 로드하여 HTTPS 엔드포인트를 노출하고 해당 트래픽을 암호화해야하는 경우 내부 HTTPS에서 애플리케이션에 내부적으로 통신하게끔 해줌</li>
<li>지역 제한을 걸 수 있음<ul>
<li><strong>화이트리스트</strong>를 이용해 허용된 국가의 유저들만 사용하게 할 수 있음</li>
<li><strong>블랙리스트</strong>를 이용해 특정 국가 유저들은 접근불가하게 만들 수 있음</li>
<li>수신되는 IP와 리스트를 비교하여 국가를 알아냄</li>
</ul>
</li>
</ul>
<br />  

<h3 id="가격-등급요금">가격 등급/요금</h3>
<ul>
<li>엣지 로케이션마다 데이터 전송 비용이 다름  </li>
<li>엣지 로케이션 수를 줄이는 방법(등급)<ul>
<li>Price Class All</li>
<li>Price Class 200 대부분 리전은 사용가능</li>
<li>Price Class 100 저렴한 리전만 사용가능</li>
</ul>
</li>
</ul>
<p><br /><hr><br />  </p>
<h2 id="cloudfront의-기술">CloudFront의 기술</h2>
<h3 id="cache-invalidations캐시-무효화">Cache Invalidations(캐시 무효화)</h3>
<p>백엔드에서 값을 업데이트할때 CloudFront의 각 엣지 로케이션은 해당 업데이트를 모름. 단지 TTL이 만료되었을때 캐시를 지울뿐이다.<br>이러한 문제점으로 TTL이 지나기 전에 저장되어있는 캐시를 지우는 방법을 지원해준다.  </p>
<h3 id="cloudfront-signed-url">CloudFront Signed URL</h3>
<p>요청을 수행하는데 필요한 제한된 권한과 시간을 제공하는 URL이다. 콘텐츠에 액세스할 수 있는 대상을 제어하는 기능으로써 동작한다. 개별 파일에 대한 액세스를 제공한다.<br>S3 버킷뿐만 아니라 백엔드 등에서 원하는 것을 일정 기간동안 사용할 수 있다.<br>유료 동영상 접근 권한과 같은 곳에 사용할 수 있다.  </p>
<p><code>S3 pre-signed URL</code>과 유사하지만 이 기능은 S3 버킷에 한정되며 S3에 직접 액세스한다는 차이가 있다.  </p>
<br />  

<h3 id="cloudfront-signed-cookies">CloudFront Signed cookies</h3>
<p>마찬가지로 콘텐츠 액세스할 수 있는 대상을 제어하는 기능이다.<br>제한된 파일 여러개에 대한 액세스 권한을 제공하고자 할때 사용한다.  </p>
<p><br /><hr><br />  </p>
<h2 id="cloudfront-vs-다른-서비스">CloudFront vs 다른 서비스</h2>
<h3 id="cloudfront-vs-s3-cross-region-replication">CloudFront vs S3 cross region replication</h3>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">CloudFront</th>
<th align="left">S3 cross region replication</th>
</tr>
</thead>
<tbody><tr>
<td align="left">특징</td>
<td align="left">엣지 로케이션에 캐시</td>
<td align="left">여러리진에 S3 복사본 존재, 실시간 업데이트</td>
</tr>
<tr>
<td align="left">사용목적</td>
<td align="left">정적컨텐츠의 빠른 로딩</td>
<td align="left">읽기전용으로 읽기 속도향상</td>
</tr>
<tr>
<td align="left">사용처</td>
<td align="left">캐시를 이용한 성능향상</td>
<td align="left">동적컨텐츠 로드</td>
</tr>
</tbody></table>
<br />

<h3 id="cloudfront-vs-aws-global-accelerator">CloudFront vs AWS Global Accelerator</h3>
<p>두 서비스 모두<br>글로벌 네트워크로 전세계의 엣지로케이션을 사용한다는 것과
DDos 보호를 위해 AWS Shield를 사용한다.<br>그렇다면 차이점은 무엇일까</p>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">CloudFront</th>
<th align="left">AWS Global Accelerator</th>
</tr>
</thead>
<tbody><tr>
<td align="left">특징</td>
<td align="left">HTTP,HTTPS 컨텐츠 캐시로 빠른 전송</td>
<td align="left">TCP, UDP의 모든 트래픽에 대해 빠른 전송</td>
</tr>
<tr>
<td align="left">접근방식</td>
<td align="left">DNS를 통한 실시간 최적의 엣지 로케이션 IP 제공</td>
<td align="left">Anycast IP를 통한 클라이언트의 접근</td>
</tr>
<tr>
<td align="left">요청처리</td>
<td align="left">엣지로케이션, 캐시없을 경우 어플리케이션</td>
<td align="left">어플리케이션</td>
</tr>
<tr>
<td align="left">사용목적</td>
<td align="left">캐시를 이용한 성능향상</td>
<td align="left">네트워크 라우팅에 의한 지연 최소화, 신속한 리전 장애 조치가 필요할때</td>
</tr>
<tr>
<td align="left">사용처</td>
<td align="left">이미지, 동영상같은 정적컨텐츠 로딩</td>
<td align="left">캐시불가, 게임이나 IoT, Voice Over Ip 등에 사용</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>💡 Unicast IP와 Anycast IP</strong><br>Unicast IP : 하나의 서버가 하나의 IP 주소를 가지는 것<br>Anycast IP : 모든 서버가 동일한 IP 주소를 가지며 클라이언트가 접근시 가장 가까운 서버로 라우팅 됨.  </p>
</blockquote>
<p><br /><hr><br /></p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/Introduction.html">AWS CloudFront 공식문서</a></li>
<li><a href="https://www.udemy.com/course/best-aws-certified-solutions-architect-associate/">유데미 강좌 - AWS Certified Solutions Architect Associate</a></li>
<li><a href="https://dev.classmethod.jp/articles/summit_korea_rapidly_transfer_content/">다양한 컨텐츠를 빠르게 전송하기</a></li>
<li><a href="https://velog.io/@combi_jihoon/CloudFront">Cloud Front</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 람다에 대해 알아보자]]></title>
            <link>https://velog.io/@rokwon_k/%EB%9E%8C%EB%8B%A4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@rokwon_k/%EB%9E%8C%EB%8B%A4%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 16 Dec 2022 07:26:43 GMT</pubDate>
            <description><![CDATA[<p>이제는 람다라는 이름이 익숙하게 들려온다. 그만큼이나 여러곳에서 유용하게 쓰이고 있다고 생각한다.<br>실제로 람다를 이용한 서버리스도 만들어 운영해보았지만 지식이 제대로 정리가 되어 있지 않아 이번 기회에 정리해본다.
서버리스에 대한 이해, 람다의 특장단점, 람다의 단점을 해결하기 위해 나온 확장기능에 대해 알아보자.</p>
<br />

<h2 id="서버리스란">서버리스란</h2>
<p>람다는 서버리스 컴퓨팅 서비스이다. 서버리스란 뭘까?<br>서버리스란 <strong>개발자가 서버를 관리할 필요없이 클라우드에게 서버 관리를 맡기는 것</strong>이다. 이로 인해 개발자는 서버관리에 시간을 쏟지 않고 코드에만 집중할 수 있게된다.</p>
<br />  

<h3 id="서버리스-종류">서버리스 종류</h3>
<p>서버리스는 개발자가 서버를 관리하지 않을 뿐 결국 클라우드 서비스가 처리하는 서버를 관리한다.<br>서버리스 컴퓨팅 기술은 클라우드가 어떤 방식으로 관리를 하는지를 기준으로 BaaS와 FaaS로 나눌 수 있다.  </p>
<blockquote>
<p><strong>💡 BaaS(Backend as a Service)</strong>
보통의 서버 개발을 할때 서버뿐만이 아니라 데이터 저장을 위한 데이터베이스, 유저인증기술, 이미지를 저장할 공간 등도 함께 구성해야한다.<br>BaaS는 위처럼 서버 개발에 필요한 데이터베이스, SNS로그인 연동, 객체 저장소 등을 자체 API로 제공을 해주는 서비스이다. 대표적으로 Firebase와 같은 서비스가 있다.  </p>
</blockquote>
<blockquote>
<p><strong>💡 FaaS(Function as a Servcie)</strong><br>프로그래밍에서 사용하는 메서드와 같이 함수를 서비스로 제공하는 것을 말한다.<br>다시 말해 <strong>실행가능한 코드 덩어리 자체가 서비스</strong>이다.<br>어떠한 이벤트를 받게 되면 등록된 함수를 실행하는 구조이다.<br>AWS의 람다와 구글의 클라우드 펑션 등이 있다.  </p>
</blockquote>
<br />

<h3 id="그래서-람다는">그래서 람다는?</h3>
<p>위에서 보았듯이 람다는 AWS가 제공하는 FaaS 서버리스 기술이다.<br>여러가지 함수들을 등록할 수 있으며 특정 이벤트로 인해 각각의 함수들이 실행된다.<br>현재는 Go, Java, JavaScript, Python, Ruby 등 다수의 언어로 람다함수를 만들 수 있다.  </p>
<p><br /><hr><br /></p>
<h2 id="람다의-특징">람다의 특징</h2>
<h3 id="람다-실행하기">람다 실행하기</h3>
<p>람다는 이벤트를 기반하여 즉시 실행된다. 곧바로 람다를 실행시킬 수도 혹은 이벤트를 구독하여 비동기적으로 람다를 실행시킬수도 있다. 대표적으로 아래와 같은 방법으로 lambda 함수를 사용한다.</p>
<p><img src="https://user-images.githubusercontent.com/52196792/208245635-821df6d4-4072-41cf-aa5b-8efd2db2ca49.png" alt="withLambda"></p>
<ul>
<li>CloudWatch + Lambda<ul>
<li>AWS CloudWatch에 로그이벤트(특정 패턴 등록도 가능)에 대응</li>
</ul>
</li>
<li>S3 + Lambda<ul>
<li>AWS S3의 특정이벤트(이미지 업로드 등)에 대응</li>
</ul>
</li>
<li>API Gateway + Lambda<ul>
<li>AWS API Gateway로 들어오는 HTTP 요청을 람다 함수를 사용해 응답처리</li>
</ul>
</li>
<li>SQS + Lambda<ul>
<li>AWS SQS 등으로 들어온 이벤트를 확인 후 비동기로 람다 함수를 이용해 작업처리</li>
</ul>
</li>
</ul>
<br />

<h3 id="장점">장점</h3>
<ul>
<li>사용한 만큼만 돈을 냄(비용 절감)<ul>
<li>지속적으로 돌아가는 서버에 비해 사용할때만 실행하는 람다는 실행횟수에 비례하여 비용을 청구</li>
<li>100만번 실행당 0.2달러로 매우 쌈</li>
</ul>
</li>
<li>자동 스케일업, 높은 가용성<ul>
<li>요청이 들어올때 동적으로 자원을 할당하고 스케일링을 처리함</li>
<li>많은 요청이 들어오면 그만큼 요금이 더 청구되기도 함</li>
</ul>
</li>
<li>빠른 제품 출시<ul>
<li>서버 관리에 신경쓸 필요없이 코드에만 집중할 수 있음</li>
</ul>
</li>
</ul>
<br />

<h3 id="단점">단점</h3>
<ul>
<li>cold start(람다가 깰때 걸리는 시간)<ul>
<li>람다는 평소에 잠자고 있다 실행될때 깨서 일을 처리함.</li>
<li>이때 지연시간이 최대 1초까지 발생함</li>
<li>지연이 허용되지 않는 서비스일 경우(실시간 서비스) 적합하지 않음</li>
</ul>
</li>
<li>서버 제공자(aws, google 등)에 의존 <ul>
<li>플랫폼에 의존하기 때문에 플랫폼을 옮겨기 쉽지 않음.</li>
</ul>
</li>
<li>긴 시간을 소요하는 작업에 불리(동영상 업로드)<ul>
<li>람다함수 호출 시 사용할 수 있는 메모리 및 시간에 제한이 있음.</li>
<li>큰 기능이라면 잘게 나누어 구현해야함</li>
</ul>
</li>
<li>코드 재사용이 어려움<ul>
<li>각 함수가 독립적이기 때문에 공통으로 사용하는 로직도 중복해서 작성하게 됨</li>
</ul>
</li>
</ul>
<p><br /><hr><br /></p>
<h2 id="람다의-효율을-위한-확장-기능">람다의 효율을 위한 확장 기능</h2>
<p>위에서 보았듯 람다는 cold start로 인한 지연문제, 서버 제공자에 대한 의존 문제 등 몇몇가지 고질적인 문제를 가지고 있다. 하지만 역시 이러한 문제들을 해소하기 위해 여러기술들이 나왔다. 뿐만 아니라 람다를 더 편리하게 개발하고 활용한 기술들도 등장하였다.</p>
<br />

<h3 id="람다-레이어">람다 레이어</h3>
<p>위에서 살펴본 단점 중 코드 재사용이 어렵다는 문제가 있다. 이를 해소하기 위한 기술이다.<br>공통된 부분을 레이어로 만들고 람다 함수들이 이를 공유해서 사용할 수 있다.<br>같은 계정뿐 아니라 다른 계정들 사이에서도 서로 공유할 수 있다.  </p>
<p><a href="https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/configuration-layers.html">람다 레이어 살펴보기</a></p>
<br />

<h3 id="프로비저닝-컨커런시">프로비저닝 컨커런시</h3>
<p>람다의 cold start의 문제점을 해소하기 위한 기술이다.<br>미리 람다함수를 실행할 수 있도록 준비해두는 기술로 동시 호출과 지연시간이 중요할 경우 사용할 수 있다.  </p>
<br />

<h3 id="스텝-펑션">스텝 펑션</h3>
<p>람다 함수들을 조합해서 하나의 워크플로우를 구성할 수 있는 기술이다.<br>SNS, SQS, 다이나모 DB 등 여러 서비스와 통합해서 사용할 수 있다.  </p>
<br />

<h3 id="rds-프록시">RDS 프록시</h3>
<p>RDS 데이터베이스 사이에서 커넥션을 관리해준다.<br>서버의 경우 커넥션의 수를 자체적으로 관리하지만 람다의 경우는 그렇지 않다.<br>이를 위해 RDS와 람다 사이에 RDS 프로시를 두고 RDS 프록시로 커넥션을 관리한다.  </p>
<br />

<h3 id="serverless-framework">Serverless Framework</h3>
<p>람다 설정, 배포 등의 작업을 할때 일일이 AWS 콘솔로 업로드하기에는 힘들다.<br>코드로 명시하고 명령어로 작업할 수 있도록 도와주는 IaC(Infrastructure as Code) 도구이다.<br>AWS SAM과 같은 도구들도 있다.  </p>
<p><a href="https://www.serverless.com/">서버리스 프레임워크 살펴보기</a></p>
<br />

<h3 id="람다-엣지">람다 엣지</h3>
<p>람다 엣지는 AWS CloudFront에서 제공해주는 기능이다.<br>CloudFront는 파일을 전송해주는 캐시 서버이지만 람다엣지를 활용하여 이미지 업로드, 변환 등의 처리 기능도 추가할 수 있다.</p>
<p><br /><hr><br /> </p>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://www.44bits.io/ko/keyword/aws-lambda">람다란</a></li>
<li><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-%EC%84%9C%EB%B2%84%EB%A6%AC%EC%8A%A4ServerLess-%EA%B0%9C%EB%85%90-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC-BaaS-FaaS">서버리스 개념정리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[js 뽀개기 - 표준 빌트인 객체(1)]]></title>
            <link>https://velog.io/@rokwon_k/js-%EB%BD%80%EA%B0%9C%EA%B8%B0-%ED%91%9C%EC%A4%80-%EB%B9%8C%ED%8A%B8%EC%9D%B8-%EA%B0%9D%EC%B2%B41</link>
            <guid>https://velog.io/@rokwon_k/js-%EB%BD%80%EA%B0%9C%EA%B8%B0-%ED%91%9C%EC%A4%80-%EB%B9%8C%ED%8A%B8%EC%9D%B8-%EA%B0%9D%EC%B2%B41</guid>
            <pubDate>Tue, 22 Nov 2022 02:21:04 GMT</pubDate>
            <description><![CDATA[<h1 id="number">Number</h1>
<blockquote>
<p><strong>💡 래퍼객체</strong><br>래퍼객체란 원시 타입에 대응되는 객체이다.<br>원시값에 내장메서드를 이용하게되면 잠시 객체 값으로 임시변환해주는데 이를 래퍼객체라고 한다.(new + 생성자함수로 직접 생성할 수도 있다.)<br>내장메서드를 사용할때 순간적으로 사용했다가 사라진다.  </p>
</blockquote>
<br />

<h3 id="number-생성자-함수">Number 생성자 함수</h3>
<p>new 연산자와 함께 호출하면 <code>[[NumberData]]</code> 내부 슬롯에 0을 할당한 Number 래퍼 객체를 생성한다.<br>-&gt; 만약 생성자 함수의 인수로 숫자를 전달한다면 인수로 전달받은 숫자를 할당한 객체를 생성한다.<br>-&gt; 인수로 숫자가 아닌 값을 전달하면 숫자로 강제변환한다. 숫자가 아니라면 <code>NAN</code>이 저장된다.  </p>
<pre><code class="language-js">const numObj1 = new Number();
console.log(numObj1); // Number {[[PrimitiveValue]]: 0}

const numObj2 = new Number(10);
console.log(numObj2); // Number {[[PrimitiveValue]]: 10}

let numObj3 = new Number(&#39;10&#39;);
console.log(numObj3); // Number {[[PrimitiveValue]]: 10}
numObj3 = new Number(&#39;Hello&#39;);
console.log(numObj3); // Number {[[PrimitiveValue]]: NaN}</code></pre>
<br />  

<h3 id="number-프로퍼티">Number 프로퍼티</h3>
<ol>
<li><code>Number.EPSILON</code><ul>
<li>1과 1보다 큰 숫자 중에서 가장 작은 숫자와의 차와 같다.</li>
<li>부동 소수점으로 발생하는 오차를 해결하기 위해 사용</li>
</ul>
</li>
<li><code>Number.MAX_VALUE</code><ul>
<li>js에서 표현할 수 있는 가장 큰 양수 값</li>
<li>이 수보다 큰 숫자는 <code>Infinitiy</code>이다.</li>
</ul>
</li>
<li><code>Number.MIN_VALUE</code><ul>
<li>js에서 표현할 수 있는 가장 작은 양수 값이다.</li>
<li>이보다 가장 작은 숫자는 0이다.</li>
</ul>
</li>
<li><code>Number.MAX_SAFE_INTEGER</code><ul>
<li>js에서 안전하게 표현할 수 있는 가장 큰 정수값이다.</li>
</ul>
</li>
<li><code>Number.MIN_SAFE_INTEGER</code><ul>
<li>js에서 안전하게 표현할 수 있는 가장 작은 정수값이다.</li>
</ul>
</li>
<li><code>Number.POSITIVE_INFINITY</code><ul>
<li>양의 무한대를 나타내는 숫자값 <code>Infinity</code>와 같다.</li>
</ul>
</li>
<li><code>Number.NEGATIVE_INFINITY</code><ul>
<li>음의 무한대를 나타내는 숫자값 <code>-Infinity</code>와 같다.</li>
</ul>
</li>
<li><code>Number.NaN</code><ul>
<li>숫자가 아님을 나타내는 숫자값이다. <code>window.NaN</code>와 같다.</li>
</ul>
</li>
</ol>
<br />  

<h3 id="number-메서드">Number 메서드</h3>
<ol>
<li><code>Number.isFinite</code><ul>
<li>ES6에 도입된 메서드로 인수로 전달된 숫자값이 유한수 인지를 판별하여 true/false를 반환한다.</li>
<li>인수가 NaN이면 언제나 false</li>
<li>빌트인 전역 함수 <code>isFinite</code>와 차이가 있다. -&gt; Number.isFinite는 숫자가 아니면 무조건 false isFinite는 암묵적 타입변환을 시행해줌.<pre><code class="language-js">Number.isFinite(0);                // -&gt; true
Number.isFinite(Number.MAX_VALUE); // -&gt; true
Number.isFinite(Infinity);  // -&gt; false</code></pre>
</li>
</ul>
</li>
<li><code>Number.isInteger</code><ul>
<li>ES6에 도입된 메서드로 정수인지를 검사한다.</li>
<li>암묵적 타입 변환 X<pre><code class="language-js">Number.isInteger(123)   // -&gt; true
Number.isInteger(&#39;123&#39;) // -&gt; false
Number.isInteger(Infinity)  // -&gt; false</code></pre>
</li>
</ul>
</li>
<li><code>Number.isNaN</code><ul>
<li>NaN인지를 검사한다.</li>
<li>빌트인 전역 함수 <code>isNaN</code>과 차이가 있다. 빌트인 함수는 전달받은 인수를 숫자로 암묵적 타입변환을 진행한다.</li>
<li>빌트인 전역 함수 <code>isNaN</code>은 undefined의 경우 true를 뿜는다.</li>
</ul>
</li>
<li><code>Number.isSafeInteger</code><ul>
<li>안전한 정수인지 검사한다. <code>-(2^53 - 1) &lt; x &lt; 2^53 - 1</code> x가 안전한 정수다.</li>
<li>암묵적 타입 변환 X</li>
</ul>
</li>
<li><code>Number.prototype.toExponential</code><ul>
<li>지수 표기법(10 n승으로 표기)으로 변환하여 문자열로 반환한다.</li>
<li>인수로 소주점 이하로 표현할 자릿수를 전달할 수 있다.</li>
<li>그룹 연산자를 사용하지 않으면 접근 연사자인지 부동소수점 표시인지 js엔진이 알아차릴 수 없으므로 그룹연산자를 사용해야한다.(공백을 이용하면 접근 연사자로 해석하니 가능하긴 하다.)<pre><code class="language-js">(77.1234).toExponential();  // -&gt; &quot;7.71234e+1&quot;
(77.1234).toExponential(4); // -&gt; &quot;7.7123e+1&quot;
(77.1234).toExponential(2); // -&gt; &quot;7.71e+1&quot;
77.toExponential(); // -&gt; SyntaxError: Invalid or unexpected token
77 .toExponential(); // -&gt; &quot;7.7e+1&quot;</code></pre>
</li>
</ul>
</li>
<li><code>Number.prototype.toFixed</code><ul>
<li>숫자를 반올림하여 문자열로 반환한다.</li>
<li>반올림하는 소수점 이하 자릿수를 나타내는 0~20 사이의 정수값을 인수로 전달할 수 있다.<pre><code class="language-js">// 소수점 이하 반올림. 인수를 생략하면 기본값 0이 지정된다.
(12345.6789).toFixed(); // -&gt; &quot;12346&quot;
// 소수점 이하 1자리수 유효, 나머지 반올림
(12345.6789).toFixed(1); // -&gt; &quot;12345.7&quot;
// 소수점 이하 2자리수 유효, 나머지 반올림
(12345.6789).toFixed(2); // -&gt; &quot;12345.68&quot;</code></pre>
</li>
</ul>
</li>
<li><code>Number.prototype.toPrecision</code><ul>
<li>인수로 전달받은 전체 자릿수까지 유효하도록 나머지 자릿수를 반올림하여 문자열로 반환한다.  </li>
<li>전체 자릿수를 나타내는 0~21 사이의 정수값을 인수로 전달할 수 있다.<pre><code class="language-js">(12345.6789).toPrecision(); // -&gt; &quot;12345.6789&quot;
// 전체 1자리수 유효, 나머지 반올림
(12345.6789).toPrecision(1); // -&gt; &quot;1e+4&quot;
// 전체 2자리수 유효, 나머지 반올림
(12345.6789).toPrecision(2); // -&gt; &quot;1.2e+4&quot;</code></pre>
</li>
</ul>
</li>
<li><code>Number.prototype.toString</code><ul>
<li>숫자를 문자열로 반환한다.</li>
<li>진법을 나타내는 2~36 사이의 정수값을 인수로 전달할 수 있고 기본값은 10진법이다.<pre><code class="language-js">(10).toString(); // -&gt; &quot;10&quot;
// 2진수 문자열을 반환한다.
(16).toString(2); // -&gt; &quot;10000&quot;
// 8진수 문자열을 반환한다.
(16).toString(8); // -&gt; &quot;20&quot;</code></pre>
</li>
</ul>
</li>
</ol>
<br />  
<hr />
<br />  

<h1 id="math">Math</h1>
<p>수학적인 상수와 함수를 위한 프로퍼티와 메서드를 제공한다. Math는 생성자 함수가 아니기 때문에 정적 프로퍼티와 정적 메서드만 제공한다.  </p>
<br />  

<h3 id="math-프로퍼티">Math 프로퍼티</h3>
<p><code>Math.PI</code> 는 원주율 PI값을 반환한다.</p>
<pre><code class="language-js">Math.PI; // -&gt; 3.141592653589793</code></pre>
<br />  

<h3 id="math-메서드">Math 메서드</h3>
<ol>
<li><p><code>Math.abs</code></p>
<ul>
<li>인수로 전달된 숫자의 절대값을 반환한다.</li>
<li>암묵적 타입 변환 O<pre><code class="language-js">Math.abs(-1);        // -&gt; 1
Math.abs(&#39;&#39;);        // -&gt; 0
Math.abs([]);        // -&gt; 0
Math.abs(null);      // -&gt; 0
Math.abs(undefined); // -&gt; NaN
Math.abs({});        // -&gt; NaN
Math.abs(&#39;string&#39;);  // -&gt; NaN
Math.abs();          // -&gt; NaN</code></pre>
</li>
</ul>
</li>
<li><p><code>Math.round</code></p>
<ul>
<li>소수점 이하를 반올림한 정수를 반환</li>
<li>암묵적 타입 변환 O<pre><code class="language-js">Math.round(1.4);  // -&gt; 1
Math.round(1.6);  // -&gt; 2
Math.round(-1.4); // -&gt; -1</code></pre>
</li>
</ul>
</li>
<li><p><code>Math.ceil</code></p>
<ul>
<li>소수점 이하를 올림 정수를 반환</li>
<li>암묵적 타입 변환 O</li>
</ul>
</li>
<li><p><code>Math.floor</code></p>
<ul>
<li>소수점 이하를 내림한 정수를 반환</li>
<li>암묵적 타입 변환 O</li>
</ul>
</li>
<li><p><code>Math.sqrt</code></p>
<ul>
<li>숫자의 제곱근을 반환한다.</li>
<li>암묵적 타입 변환 O<pre><code class="language-js">Math.sqrt(9);  // -&gt; 3
Math.sqrt(-9); // -&gt; NaN
Math.sqrt(2);  // -&gt; 1.414213562373095</code></pre>
</li>
</ul>
</li>
<li><p><code>Math.random</code></p>
<ul>
<li>임의의 난수를 반환 <code>0 &lt;= x &lt; 1</code><pre><code class="language-js">Math.random();</code></pre>
</li>
</ul>
</li>
<li><p><code>Math.pow</code></p>
<ul>
<li>첫번째 인수를 밑으로 두 번째 인수를 지수로 거듭제곱한 결과를 반환한다.</li>
<li>ES7에서 도입된 지수 연산자를 사용하면 가독성이 더 좋다.</li>
<li>암묵적 타입 변환 O<pre><code class="language-js">Math.pow(2, 8);  // -&gt; 256
Math.pow(2, -1); // -&gt; 0.5
// ES7 지수 연산자
2 ** 2 ** 2; // -&gt; 16</code></pre>
</li>
</ul>
</li>
<li><p><code>Math.max</code></p>
<ul>
<li><p>전달받은 인수 중 가장 큰 수를 반환한다.(인수를 전달하지 않으면 -Infinity 반환)</p>
</li>
<li><p>배열을 인수로 전달받는 방법도 있다.</p>
<pre><code class="language-js">Math.max(1); // -&gt; 1
Math.max(1, 2); // -&gt; 2
Math.max(1, 2, 3); // -&gt; 3
// 배열 요소 중에서 최대값 취득
Math.max.apply(null, [1, 2, 3]); // -&gt; 3

// ES6 스프레드 문법
Math.max(...[1, 2, 3]); // -&gt; 3</code></pre>
</li>
</ul>
</li>
<li><p><code>Math.min</code></p>
<ul>
<li>전달받은 인수 중 가장 작은 수를 반환한다.(인수를 전달하지 않으면 Infinity 반환)  </li>
</ul>
</li>
</ol>
<br />  
<hr />
<br />  

<h1 id="date">Date</h1>
<p>Date는 날짜와 시간을 위한 메서드를 제공하는 빌트인 객체이면서 생성자 함수이다.<br>UTC(협정 세계시)는 국제 표준시, GMT(그리니치 평균시)로 불리기도 한다. KST(한국 표준시간)은 UTC에 9시간을 더한 시간이다.<br>현재 날짜와 시간은 코드가 실행된 시스템의 시계에 의해 결정된다.  </p>
<br />  

<h3 id="date-생성자-함수">Date 생성자 함수</h3>
<ol>
<li><p><code>new Date()</code>  </p>
<ul>
<li>현재 날짜와 시간을 가지는 Date 객체를 반환한다.  </li>
<li>Date객체는 내부적으로 날짜와 시간을 나타내는 정수값을 갖지만 출력시 날짜와 시간 정보를 출력한다.  </li>
<li>new를 사용하면 객체가 아닌 문자열로 반환한다.<pre><code class="language-js">new Date(); // -&gt; Mon Jul 06 2020 01:03:18 GMT+0900 (대한민국 표준시)
Date(); // -&gt; &quot;Mon Jul 06 2020 01:10:47 GMT+0900 (대한민국 표준시)&quot;</code></pre>
</li>
</ul>
</li>
<li><p><code>new Date(milliseconds)</code></p>
<ul>
<li>밀리초를 인수로 전달하면 1970년 1월 1일 00:00:00(UTC)을 기점으로 전달된 밀리초만큼 경과된 날짜와 시간을 나타내는 Date객체를 반환한다.<pre><code class="language-js">new Date(86400000); // -&gt; Fri Jan 02 1970 09:00:00 GMT+0900 (대한민국 표준시)</code></pre>
</li>
</ul>
</li>
<li><p><code>new Date(dateString)</code></p>
<ul>
<li>날짜와 시간을 나타내는 문자열을 인수로 전달하면 지정된 날짜의 Date 객체를 반환한다.</li>
<li>이때 전달한 문자열은 <code>Date.parse</code> 메서드에 의해 해석가능한 형식이어야한다.<pre><code class="language-js">new Date(&#39;May 26, 2020 10:00:00&#39;);
// -&gt; Tue May 26 2020 10:00:00 GMT+0900 (대한민국 표준시)</code></pre>
</li>
</ul>
</li>
<li><p><code>new Date(year, month[,day, hour, minute, second, millisecond])</code></p>
<ul>
<li><p>연,월,일,시,분,초,밀리초를 인수로 전달하면 지정된 날짜와 시간을 나타내는 객에를 반환한다.</p>
</li>
<li><p>연, 월은 필수이며 지정하지 않은 옵션은 0 또는 1로 초기화한다.</p>
<pre><code class="language-js">// 월을 나타내는 2는 3월을 의미한다. 2020/3/1/00:00:00:00
new Date(2020, 2);
// -&gt; Sun Mar 01 2020 00:00:00 GMT+0900 (대한민국 표준시)
// 월을 나타내는 2는 3월을 의미한다. 2020/3/26/10:00:00:00
new Date(2020, 2, 26, 10, 00, 00, 0);
// -&gt; Thu Mar 26 2020 10:00:00 GMT+0900 (대한민국 표준시)

// 다음처럼 표현하면 가독성이 훨씬 좋다.
new Date(&#39;2020/3/26/10:00:00:00&#39;);
// -&gt; Thu Mar 26 2020 10:00:00 GMT+0900 (대한민국 표준시)</code></pre>
</li>
</ul>
</li>
</ol>
<br />  

<h3 id="date-메서드">Date 메서드</h3>
<ol>
<li><p><code>Date.now</code></p>
<ul>
<li>기준시간(1970년 1월 1일 00:00:00(UTC))을 기점으로 현재 시간까지 경과한 밀리초를 숫자로 반환한다.</li>
</ul>
</li>
<li><p><code>Date.parse</code></p>
<ul>
<li><p>기준시간을 기점으로 인수로 전달된 지정시간까지의 밀리초를 숫자로 반환한다.</p>
<pre><code class="language-js">// UTC
Date.parse(&#39;Jan 2, 1970 00:00:00 UTC&#39;); // -&gt; 86400000

// KST
Date.parse(&#39;Jan 2, 1970 09:00:00&#39;); // -&gt; 86400000

// KST
Date.parse(&#39;1970/01/02/09:00:00&#39;);  // -&gt; 86400000</code></pre>
</li>
</ul>
</li>
<li><p><code>Date.UTC</code></p>
<ul>
<li>기준시간을 기점으로 인수로 전달된 지정시간까지의 밀리초를 숫자로 반환한다.</li>
<li>new Date(year, month, ...) 와 같은 형식의 인수를 사용해야한다.</li>
<li>인수는 로컬 타임이 아닌 UTC로 인식된다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getFullYear</code></p>
<ul>
<li>Date 객체의 연도를 나타내는 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setFullYear</code></p>
<ul>
<li>Date 객체에 연도를 나타내는 정수를 설정한다. 연도 이외에 옵션으로 월,일도 설정할 수 있다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getMonth</code></p>
<ul>
<li>Date 객체의 월을 나타내는 0~11의 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setMonth</code></p>
<ul>
<li>Date 객체의 월을 설정한다. 월 이외에 일도 설정할 수 있다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getDate</code></p>
<ul>
<li>Date 객체의 날짜를 나타내는 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setDate</code></p>
<ul>
<li>Date 객체의 날짜를 설정한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getDay</code></p>
<ul>
<li>Date 객체의 요일을(0~6) 정수로 반환한다. 0이 일요일, 6이 토요일이다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getHours</code></p>
<ul>
<li>Date 객체의 시간(0 ~ 23)을 나타내는 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setHours</code></p>
<ul>
<li>Date 객체의 시간을 설정한다. 분, 초, 밀리초도 설정할 수 있다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getMinutes</code></p>
<ul>
<li>Date 객체의 분(0 ~ 59)을 나타내는 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setMinutes</code></p>
<ul>
<li>Date 객체의 분을 설정한다. 초, 밀리초도 설정할 수 있다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getSeconds</code></p>
<ul>
<li>Date 객체의 초(0 ~ 59)를 나타내는 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setSeconds</code></p>
<ul>
<li>Date 객체의 초을 설정한다. 밀리초도 설정할 수 있다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getMilliseconds</code></p>
<ul>
<li>Date 객체의 밀리초(0 ~ 999)를 나타내는 정수를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setMilliseconds</code></p>
<ul>
<li>Date 객체의 밀리초을 설정한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getTime</code></p>
<ul>
<li>기준점을 기점으로 Date 객체의 시간까지 경과된 밀리초를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.setTime</code></p>
<ul>
<li>기준점을 기점으로 Date 객체의 시간까지 경과된 밀리초를 설정한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.getTimezoneOffset</code></p>
<ul>
<li><p>UTC와 Date 객체에 지정된 로캘 시간과의 차이를 분 단위로 반환한다.</p>
<pre><code class="language-js">const today = new Date(); // today의 지정 로캘은 KST다.

//UTC와 today의 지정 로캘 KST와의 차이는 -9시간이다.
today.getTimezoneOffset() / 60; // -9</code></pre>
</li>
</ul>
</li>
<li><p><code>Date.prototype.toDateString</code></p>
<ul>
<li>사람이 읽을 수 있는 형식의 문자열로 Date 객체의 날짜를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.toTimeString</code></p>
<ul>
<li>사람이 읽을 수 있는 형식의 문자열로 Date 객체의 시간를 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.toISOString</code></p>
<ul>
<li>ISO 8601 형식으로 Date 객체의 날짜와 시간을 표현한 문자열을 반환한다.</li>
</ul>
</li>
<li><p><code>Date.prototype.toLocaleString</code></p>
<ul>
<li><p>인수로 전달한 로캘을 기준으로 Date 객체의 날짜와 시간을 표현한 문자열을 반환한다.</p>
</li>
<li><p>인수가 생략되면 동작 중인 시스템의 로캘을 적용한다.</p>
<pre><code class="language-js">const today = new Date(&#39;2020/7/24/12:30&#39;);

today.toString(); // -&gt; Fri Jul 24 2020 12:30:00 GMT+0900 (대한민국 표준시)
today.toLocaleString(); // -&gt; 2020. 7. 24. 오후 12:30:00
today.toLocaleString(&#39;ko-KR&#39;); // -&gt; 2020. 7. 24. 오후 12:30:00</code></pre>
</li>
</ul>
</li>
</ol>
<br />  
<hr />
<br />  

<h1 id="regexp">RegExp</h1>
<blockquote>
<p><strong>💡 정규 표현식(regular expression)</strong><br>일정한 패턴을 가진 문자열의 집합을 표현하기 위해 사용하는 형식언어.<br>문자열을 대상으로 패턴 매칭 기능을 제공한다.(특정 패턴과 일치하는 문자열을 검색하거나 추출, 치환 가능한 기능)<br><strong>반복문과 조건문 없이 패턴을 정의하고 테스트하는 것으로 간단히 체크할 수 있다.</strong><br><strong>주석이나 공백을 허용하지 않고 여러가지 기호를 혼합하여 사용하기 때문에 가독성이 좋지 않다는 문제가 있다.</strong>  </p>
</blockquote>
<br />  

<h3 id="정규-표현식의-생성">정규 표현식의 생성</h3>
<p><code>정규 표현식 리터럴</code>과 <code>RegExp 생성자 함수</code>를 사용해 생성할 수 있다.<br><code>정규 표현식 리터럴</code>은 <strong>패턴과 플래그</strong>로 구성된다.  </p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;

// 패턴: is
// 플래그: i =&gt; 대소문자를 구별하지 않고 검색한다.
const regexp1 = /is/i;
// ES6의 RegExp 생성자 함수 이용
const regexp2 = new RegExp(/is/i); // ES6

// test 메서드는 target 문자열에 대해 정규표현식 regexp의 패턴을 검색하여 매칭 결과를 불리언 값으로 반환한다.
regexp1.test(target); // -&gt; true
regexp2.test(target); // -&gt; true</code></pre>
<br />  

<h3 id="regexp-메서드">RegExp 메서드</h3>
<ol>
<li><p><code>RegExp.prototype.exec</code></p>
<ul>
<li>인수로 전달받은 문자열에 대해 패턴을 검색하여 매칭 결과를 배열로 반환한다.</li>
<li>결과가 없는 경우 null을 반환한다.<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/;
regExp.exec(target); // -&gt; [&quot;is&quot;, index: 5, input: &quot;Is this all there is?&quot;, groups: undefined]</code></pre>
</li>
</ul>
</li>
<li><p><code>RegExp.prototype.test</code></p>
<ul>
<li><p>인수로 전달받은 문자열에 대해 정규 표현식의 패턴을 검색하여 매칭 결과를 불리언 값으로 반환한다.</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/;

regExp.test(target); // -&gt; true</code></pre>
</li>
</ul>
</li>
<li><p><code>String.prototype.match</code></p>
<ul>
<li><p>대상 문자열로 인수로 전달받은 정규표현식과의 매칭결과를 배열로 반환한다.</p>
</li>
<li><p>g 플래그를 지정하면 모든 매칭결과를 배열로 반환한다.</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;
const regExp = /is/;
target.match(regExp); // -&gt; [&quot;is&quot;, index: 5, input: &quot;Is this all there is?&quot;, groups: undefined]

const regExp2 = /is/g;
target.match(regExp2); // -&gt; [&quot;is&quot;, &quot;is&quot;]</code></pre>
</li>
</ul>
</li>
</ol>
<br />  

<h3 id="플래그">플래그</h3>
<p>패턴과 함께 정규 표현식을 구성하는 플래그는 정규 표현식의 검색 방식을 설정하기 위해 사용한다.<br>총 6개가 있다.</p>
<ol>
<li><code>i</code><ul>
<li>Ignore case : 대소문자를 구별하지 않고 패턴 검색</li>
</ul>
</li>
<li><code>g</code><ul>
<li>Global : 대상 문자열 내에서 패턴과 일치하는 모든 문자열을 전역 검색</li>
</ul>
</li>
<li><code>m</code><ul>
<li>Multi line : 문자열의 행이 바뀌더라도 패턴 검색을 계속한다.</li>
</ul>
</li>
</ol>
<br />  

<h3 id="패턴">패턴</h3>
<p>문자열의 일정한 규칙을 표현하기 위해 사용한다.<br><code>/</code>로 열고 닫으며 문자열의 따옴표는 생략한다. 따옴표를 포함한다면 따옴표도 패턴에 포함된다.  </p>
<ol>
<li><p><strong>임의의 문자열 검색</strong></p>
<ul>
<li><p>. 은 임의의 문자 한 개를 의미한다.</p>
<pre><code class="language-js">const target = &#39;Is this all there is?&#39;;

// 임의의 3자리 문자열을 대소문자를 구별하여 전역 검색한다.
const regExp = /.../g;

target.match(regExp); // -&gt; [&quot;Is &quot;, &quot;thi&quot;, &quot;s a&quot;, &quot;ll &quot;, &quot;the&quot;, &quot;re &quot;, &quot;is?&quot;]</code></pre>
</li>
</ul>
</li>
<li><p><strong>반복 검색</strong></p>
<ul>
<li><p>{m,n}은 최소 m번, 최대 n번 반복되는 문자열을 의미한다.  </p>
</li>
<li><p>{n}은 {n,n}과 같다.</p>
</li>
<li><p>{n,}은 최소 n번 이상 반복되는 문자열을 의미한다.</p>
</li>
<li><p><code>+</code>는 {1,}와 같다.</p>
</li>
<li><p><code>?</code>는 {0,1}과 같다.</p>
</li>
<li><p>콤마 뒤에 공백이 있으면 정상 동작하지 않으므로 주의해야한다.</p>
<pre><code class="language-js">const target = &#39;A AA B BB Aa Bb AAA&#39;;
// &#39;A&#39;가 최소 1번, 최대 2번 반복되는 문자열을 전역 검색한다.
const regExp = /A{1,2}/g;
target.match(regExp); // -&gt; [&quot;A&quot;, &quot;AA&quot;, &quot;A&quot;, &quot;AA&quot;, &quot;A&quot;]

const target2 = &#39;color colour&#39;;
// &#39;colo&#39; 다음 &#39;u&#39;가 최대 한 번(0번 포함) 이상 반복되고 &#39;r&#39;이 이어지는 문자열 &#39;color&#39;, &#39;colour&#39;를 전역 검색한다.
const regExp2 = /colou?r/g;

target2.match(regExp2); // -&gt; [&quot;color&quot;, &quot;colour&quot;]</code></pre>
</li>
</ul>
</li>
<li><p><strong>OR 검색</strong></p>
<ul>
<li><code>|</code>은 or의 이미를 갖는다.</li>
<li><code>[]</code> 내의 문자는 or로 동작한다</li>
<li>범위를 지정하려면 <code>[]</code> 내에 <code>-</code>를 사용한다.</li>
<li><code>[\d]</code>는 숫자를 의미한다.( = <code>[0-9]</code>)</li>
<li><code>[\D]</code>는 숫자가 아닌 문자를 의미한다.</li>
<li><code>[\w]</code>는 알파벳, 숫자, 언더스코어를 의미한다.( = <code>[A-Za-z0-9_]</code>)</li>
<li><code>[\W]</code>는 알파벳, 숫자, 언더스코어가 아닌 문자를 의미한다.</li>
</ul>
</li>
<li><p><strong>NOT 검색</strong></p>
<ul>
<li><code>^</code>은 not의 의미를 가진다</li>
<li><code>[^0-9]</code>는 숫자를 제외한 문자를 의미힌다.</li>
</ul>
</li>
<li><p><strong>시작 위치로 검색</strong></p>
<ul>
<li><code>[]</code> 밖의 <code>^</code>은 문자열의 시작을 의미한다.<pre><code class="language-js">const target = &#39;https://poiemaweb.com&#39;;
// &#39;https&#39;로 시작하는지 검사한다.
const regExp = /^https/;
regExp.test(target); // -&gt; true</code></pre>
</li>
</ul>
</li>
<li><p><strong>마지막 위치로 검색</strong></p>
<ul>
<li><code>$</code>는 문자열의 마지막을 의미한다.<pre><code class="language-js">const target = &#39;https://poiemaweb.com&#39;;
// &#39;com&#39;으로 끝나는지 검사한다.
const regExp = /com$/;
regExp.test(target); // -&gt; true</code></pre>
</li>
</ul>
</li>
</ol>
<br />  

<h3 id="자주-사용하는-정규표현식">자주 사용하는 정규표현식</h3>
<ol>
<li><p><strong>아이디로 사용 가능한지 검사</strong></p>
<ul>
<li><p>알파벳 대소문자, 숫자로 시작하고 끝나며 4~10자리인지 검사</p>
<pre><code class="language-js">const id = &#39;abc123&#39;;

// 알파벳 대소문자 또는 숫자로 시작하고 끝나며 4 ~ 10자리인지 검사한다.
/^[A-Za-z0-9]{4,10}$/.test(id); // -&gt; true</code></pre>
</li>
</ul>
</li>
<li><p><strong>메일 주소 형식에 맞는지 검사</strong></p>
<ul>
<li><p>인터넷 메시지 형식 규약인 RFC 5322에 맞는 정교한 패턴 매칭이 필요하다면 매우 복잡하다.</p>
</li>
<li><p>아래는 위 형식과 맞지는 않다.</p>
<pre><code class="language-js">const email = &#39;ungmo2@gmail.com&#39;;

/^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/.test(email); // -&gt; true</code></pre>
</li>
</ul>
</li>
<li><p><strong>핸드폰 번호 형식에 맞는지 검사</strong></p>
<pre><code class="language-js"> const cellphone = &#39;010-1234-5678&#39;;

 /^\d{3}-\d{3,4}-\d{4}$/.test(cellphone); // -&gt; true</code></pre>
</li>
<li><p><strong>특수 문자 포함 여부 검사</strong></p>
<pre><code class="language-js"> const target = &#39;abc#123&#39;;

 // A-Za-z0-9 이외의 문자가 있는지 검사한다.
 (/[^A-Za-z0-9]/gi).test(target); // -&gt; true</code></pre>
</li>
</ol>
<br />  
<hr />
<br />  

<h1 id="string">String</h1>
<p>표준 빌트인 객체인 String 객체는 생성자 함수 객체다.<br>String 생성자 함수에 인수를 전달하지 않고 <code>new</code>와 함게 호출하면 <code>[[SrringData]]</code> 내부 슬록에 빈 문자열을 할당한 String 래퍼 객체를 생성한다.<br>유사배열 객체이면서 이터러블이므로 인덱스를 사용하여 각문자에 접근이 가능한다.  </p>
<br />

<h3 id="string-메서드">String 메서드</h3>
<p>String 객체에는 원본 래퍼 객체를 직접 변경하는 메서드는 존재하지 않는다.<br>String 객체의 메서드는 언제나 새로운 문자열을 반환한다.<br>문자열은 변경불가능한 값이기 때문에 String 래퍼 객체도 읽기 전용 객체로 제공된다.  </p>
<ol>
<li><p><code>String.prototype.indexOf</code></p>
<ul>
<li><p>대상 문자열에서 인수로 전달받은 문자열을 검색하여 첫 번째 인덱스를 반환한다.</p>
</li>
<li><p>검색 실패 시 -1 을 반환한다.</p>
</li>
<li><p>두 번째 인수로 검색을 시작할 인덱스를 전달할 수 있다.</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

// 문자열 str에서 &#39;l&#39;을 검색하여 첫 번째 인덱스를 반환한다.
str.indexOf(&#39;l&#39;); // -&gt; 2
// 문자열 str에서 &#39;or&#39;을 검색하여 첫 번째 인덱스를 반환한다.
str.indexOf(&#39;or&#39;); // -&gt; 7
// 문자열 str의 인덱스 3부터 &#39;l&#39;을 검색하여 첫 번째 인덱스를 반환한다.
str.indexOf(&#39;l&#39;, 3); // -&gt; 3</code></pre>
</li>
</ul>
</li>
<li><p><code>String.prototype.search</code></p>
<ul>
<li><p>인수로 전달 받은 정규 표현식과 매치하는 문자열을 검색하여 일치하는 문자열의 인덱스를 반환한다.</p>
</li>
<li><p>검색 실패 시 -1 을 반환한다.</p>
<pre><code class="language-js">const str = &#39;Hello world&#39;;

// 문자열 str에서 정규 표현식과 매치하는 문자열을 검색하여 일치하는 문자열의 인덱스를 반환한다.
str.search(/o/); // -&gt; 4
str.search(/x/); // -&gt; -1</code></pre>
</li>
</ul>
</li>
<li><p><code>String.prototype.includes</code></p>
<ul>
<li>ES6에서 도입된 메서드로 인수로 전달받은 문자열이 포함되어 있는지 확인하여 true/false 로 반환한다.</li>
<li>두 번째 인수로 검색을 시작할 인덱스를 전달할 수 있다.</li>
</ul>
</li>
<li><p><code>String.prototype.startsWith</code></p>
<ul>
<li>접두사가 일치하는지 확인한여 true.false 로 반환한다.</li>
<li>두 번째 인수로 검색을 시작할 인덱스를 전달할 수 있다.</li>
</ul>
</li>
<li><p><code>String.prototype.endsWith</code></p>
<ul>
<li>접미가 일치하는지 확인한여 true.false 로 반환한다.</li>
<li>두 번째 인수로 검색할 문자열의 길이를 전달할 수 있다.</li>
</ul>
</li>
<li><p><code>String.prototype.charAt</code></p>
<ul>
<li>인수로 전달받은 인덱스에 위치한 문자를 검색하여 반환한다.</li>
<li>인덱스가 문자열의 범위를 벗어아면 빈 문자열을 반환한다.</li>
</ul>
</li>
<li><p><code>String.prototype.substring</code></p>
<ul>
<li><p>첫번째 인수(시작 인덱스) ~ 두번째 인수(끝 인덱스) 전 까지의 부분 문자열을 반환한다.</p>
</li>
<li><p>두번째 인수를 생략하면 문자열의 끝까지로 인식한다.</p>
</li>
<li><p>첫번째 인수가 두번째 인수보다 크면 교환된다.</p>
</li>
<li><p>0보다 작거나 NaN인 경우 0으로 취급한다.</p>
<pre><code class="language-js">const str = &#39;Hello World&#39;;

// 인덱스 1부터 마지막 문자까지 부분 문자열을 반환한다.
str.substring(1); // -&gt; &#39;ello World&#39;
str.substring(4, 1); // -&gt; &#39;ell&#39;
str.substring(-2); // -&gt; &#39;Hello World&#39;
str.substring(1, 100); // -&gt; &#39;ello World&#39;
str.substring(20); // -&gt; &#39;&#39;</code></pre>
</li>
</ul>
</li>
<li><p><code>String.prototype.slice</code></p>
<ul>
<li>substring 메서드와 동일하게 동작한다.(단, slice는 음수인 인수를 전달할 수 있다.)</li>
<li>인수가 음수라면 문자열의 가장 뒤부터 시작하여 문자열을 잘라내어 반환한다.</li>
</ul>
</li>
<li><p><code>String.prototype.toUpperCase</code></p>
<ul>
<li>대상 문자열을 모두 대문자로 변경하여 반환한다.</li>
</ul>
</li>
<li><p><code>String.prototype.toLowerCase</code></p>
<ul>
<li>대상 문자열을 모두 소문자로 변경하여 반환한다.</li>
</ul>
</li>
<li><p><code>String.prototype.trim</code></p>
<ul>
<li>문자열의 앞 뒤 공백을 제거하여 반환한다.</li>
</ul>
</li>
<li><p><code>String.prototype.repeat</code></p>
<ul>
<li>ES6부터 도입되었으며 인수로 전달받은 정수만큼 반복해 연결한 새로운 문자열을 반환한다.</li>
<li>인수가 0이면 빈 문자열을 반환한다.(인수 생략시 기본값은 0)</li>
<li>음수면 RangeError를 발생시킨다.</li>
</ul>
</li>
<li><p><code>String.prototype.replace</code></p>
<ul>
<li><p>첫 번째 인수로 전달받은 문자열 또는 정규표현식을 검색하여 두 번째 인수로 전달한 문자열로 치환한 문자열을 반환한다.</p>
</li>
<li><p>검색된 문자열이 여럿 존재할 경우 첫 번째로 검색된 문자열만 치환한다.</p>
<pre><code class="language-js">const str = &#39;Hello world world&#39;;

str.replace(&#39;world&#39;, &#39;Lee&#39;); // -&gt; &#39;Hello Lee world&#39;</code></pre>
</li>
</ul>
</li>
<li><p><code>String.prototype.split</code></p>
<ul>
<li><p>첫 번째 인수로 전달한 문자열 또는 정규 표현식을 검색하여 문자열을 구분한 후 분리된 각 문자열로 이루어진 배열을 반환한다.</p>
</li>
<li><p>인수로 빈 문자열을 전달하면 각 문자를 모두 분리한다.</p>
</li>
<li><p>인수를 생략하면 문자열 전체를 단일 요소로하는 배열을 반환한다.</p>
</li>
<li><p>두 번째 인수로 배열의 길이를 지정할 수 있다.</p>
<pre><code class="language-js">const str = &#39;How are you doing?&#39;;

// 공백으로 구분(단어로 구분)하여 배열로 반환한다.
str.split(&#39; &#39;); // -&gt; [&quot;How&quot;, &quot;are&quot;, &quot;you&quot;, &quot;doing?&quot;]

// \s는 여러 가지 공백 문자(스페이스, 탭 등)를 의미한다. 즉, [\t\r\n\v\f]와 같은 의미다.
str.split(/\s/); // -&gt; [&quot;How&quot;, &quot;are&quot;, &quot;you&quot;, &quot;doing?&quot;]

// 인수로 빈 문자열을 전달하면 각 문자를 모두 분리한다.
str.split(&#39;&#39;); // -&gt; [&quot;H&quot;, &quot;o&quot;, &quot;w&quot;, &quot; &quot;, &quot;a&quot;, &quot;r&quot;, &quot;e&quot;, &quot; &quot;, &quot;y&quot;, &quot;o&quot;, &quot;u&quot;, &quot; &quot;, &quot;d&quot;, &quot;o&quot;, &quot;i&quot;, &quot;n&quot;, &quot;g&quot;, &quot;?&quot;]

// 인수를 생략하면 대상 문자열 전체를 단일 요소로 하는 배열을 반환한다.
str.split(); // -&gt; [&quot;How are you doing?&quot;]</code></pre>
</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[S3 훑어보기]]></title>
            <link>https://velog.io/@rokwon_k/S3-%ED%9B%91%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@rokwon_k/S3-%ED%9B%91%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 17 Nov 2022 02:37:24 GMT</pubDate>
            <description><![CDATA[<p>Simple Storage Service의 약자
데이터를 온라인에 오브젝트 형태로 저장하는 서비스이다.
S3는 아래와 같은 특징을 가지고 있다.</p>
<p>→ 객체 스토어(스토리지)
→ 리전 내 여러 시설에 걸쳐 생성됨
→ 인터넷 액세스가 가능함
→ EC2나 EBS로 구축하는 것보다 저렴함
→ 장 용량이 사실상 무한대이며 파일 저장에 최적화 되어있음</p>
<br />

<h3 id="s3를-이루는-두가지-개념---객체과-버킷">S3를 이루는 두가지 개념 - 객체과 버킷</h3>
<p><code>객체(Object)</code>는 저장단위</p>
<p>→ 최대 5TB까지 저장가능
→ Key를 통해서 버킷에서 유일한 것으로 식별
→ 객체 자체의 ACL(파일 권한), CORS가 존재한다.
→ 파일의 정보를 담은 메타데이터(수정일, 파일 타입, 사이즈 등)</p>
<p><code>버킷(Bucket)</code>은 객체를 저장하고 관리하는 역할을 한다.</p>
<p>→ 한 리전에 여러 개 버킷이 존재할 수 있다.
→ 여러 가용영역에 걸쳐 생성된다.</p>
<br />

<h2 id="언제나-헷갈리는-버킷-정책과-접근-권한---s3-보안권한">언제나 헷갈리는 <strong>버킷 정책과 접근 권한 - S3 보안/권한</strong></h2>
<p>4가지 권한 설정방법이 있다.
 <code>Public Access</code>,  <code>ACL</code>, <code>Bucket Policy</code>,  <code>pre-signed URL</code> </p>
<br />

<h3 id="public-access">Public Access</h3>
<p>기본적으로 버킷 공개를 차단할 수 있는 방법을 제공한다.
특수한 경우에 대해 퍼블릭 엑세스가 허용되는 경우를 모두 차단할 수 있다.
(버킷의 권한 탭에서 설정할 수 있다)</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/7de88fda-c5ba-47cd-9087-d8a61c47cecd/image.png" alt="S3 Public Access"></p>
<br />

<h3 id="aclaccess-control-list">ACL(Access Control List)</h3>
<p>버킷이나 객체에 대해 <strong>요청자의 권한 허용범위를 설정</strong>할 수 있다.
요청자마다 권한 허용 범위를 설정할 수 있다. (버킷 소유자,  owner, group, 특정 유저별 설정 가능)
IP 주소나 도메인 단위로의 제어, IAM 사용자 단위로의 제어는 불가능 하다.</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/446cea43-90e7-46be-bfb8-a6022f45a9ca/image.png" alt="S3 ACL"></p>
<br />

<h3 id="bucket-policy버킷-정책">Bucket Policy(버킷 정책)</h3>
<p>버킷자체를 사용할 권한을 가진 사용자별로 권한 범위 설정 가능하다.
버킷 안의 객체 별로 권한 설정은 불가능하다.
버킷은 ACL보다 세분화된 권한을 제공하지만 객체별 권한을 설정할 순 없다.
(아래는 모든 유저에게 s3의 모든 권한을 주는 정책)</p>
<pre><code class="language-json">{
  &quot;Id&quot;: &quot;Policy1668596926629&quot;,
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;Stmt1668596922203&quot;,
      &quot;Action&quot;: &quot;s3:*&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Resource&quot;: &quot;arn:aws:s3:::the-pool-s3-1112&quot;,
      &quot;Principal&quot;: &quot;*&quot;
    }
  ]
}</code></pre>
<p><strong>Action</strong>은 어떤 작업을 허용할 것인지(get, put 등)
<strong>Effect</strong>는 이 작업을 허용할것인지 거부 할것인지
<strong>Resource</strong>는 어떤 자원에 대한 것인지(arn으로 작성)
<strong>Principal</strong>은 특정사용자에 대한 명세</p>
<br />

<h3 id="pre-signed-url">pre-signed url</h3>
<p>비공개인 객체를 일정 기간 동안 객체 다운로드를 허가해주는 기능이다.
미리 서명된 URL을 만들어 일정 기간동안만 객체를 이용할 수 있다.</p>
<br />

<h3 id="정적-웹-사이트-호스팅">정적 웹 사이트 호스팅</h3>
<p>버킷의 속성 탭 하단에 정적 웹 사이트 호스팅 기능이 있다. 해당 기능을 이용하여 호스팅을 설정할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DI에 대하여]]></title>
            <link>https://velog.io/@rokwon_k/DI%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@rokwon_k/DI%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Mon, 31 Oct 2022 05:06:54 GMT</pubDate>
            <description><![CDATA[<p>DI는 객체 지향 설계5원칙(SOLID) 중 Dependency Inversion Principle과 관련이 깊다.  </p>
<blockquote>
<p><strong>💡 Dependency Inversion Principle(의존 관계 역전원칙)</strong><br>상위 레벨의 객체가 하위 레벨의 객체에 의존하는 현상을 끊는것.<br>상위객체가 하위객체에 의존하게 된다면 하위객체가 변경될때 상위객체또한 변경해야하기 때문에 최대한 의존성을 없애는게 유지/보수에 효율적이다.  </p>
</blockquote>
<p>다시 말하자면 DI에는 <code>의존 관계 역전원칙</code>의 개념을 내포하고있다.</p>
<br />

<p>DI의 핵심개념을 나누어 설명하자면 아래와 같다고 볼 수 있다.<br><strong>내부에서 사용할(의존할) 객체를 외부에서 주입시키기</strong><br><strong>의존성 분리 - 객체에 의존하지말고 역할에 의존하자</strong>  </p>
<br />

<h3 id="내부에서-사용할-객체를-외부에서-주입시키기">내부에서 사용할 객체를 외부에서 주입시키기</h3>
<p>첫 번째로는 내부에서 사용할 객체를 외부에서 생성하여 주입시키는 방법이다.<br>하위객체(내부객체)의 생명주기를 객체 내에서 담당한다고 생각해보자.<br>객체는 오직 해당 하위객체에 완전의존하여 해당 객체가 아닌 다른 객체로의 수정이 어려워진다.  </p>
<p>즉, 외부 주입으로 얻을 수 있는 장점은 아래와 같다. </p>
<ul>
<li>객체간의 의존관계를 줄일 수 있다.</li>
<li>내부에서 객체에 의존하지않아 유닛테스트에 용이하다.  </li>
<li>코드의 단순화</li>
</ul>
<p>외부에서 주입한다고는 하나 여전히 한 타입의 객체에 의존할 수 밖에 없는 상황이다. 
의존성을 분리할 수 있는 방법이 없을까?</p>
<br />  

<h3 id="의존성-분리---객체에-의존하지말고-역할에-의존하자">의존성 분리 - 객체에 의존하지말고 역할에 의존하자</h3>
<p>외부에서 주입하더라도 한 타입의 객체만 주입할 수 있다면 유지,보수 시 상위/하위 객체를 모두 수정하게 되는 문제가 발생한다.<br>이런 문제점을 해결하기 위해방법이 바로 추상화(interface)에 의존하는 것이다.<br>객체에 의존하는 것이 아닌 역할을 정의하는 추상화에 의존함으로써 해당 역할에 대한 책임을 가지는 모든 객체로 수정이 가능해진다.<br>이 개념이 바로 <strong>IoC(Inversion Of Control)</strong>이다. 의존관계가 존재하는 두 객체를 추상화(역할)에 의존시키게 만드는 방법이다.  </p>
<p>(예시 코드)  </p>
<p>이를 통해 얻을 수 있는 장점이다.  </p>
<ul>
<li>객체간 의존관계 최소화</li>
<li>유지보수 시 유연성과 확장성을 향상시킨다.  </li>
<li>가독성이 좋아진다. 추상화 코드만으로 이해가 가능</li>
</ul>
<p>하지만 여전히 문제점은 남아있다.<br>생명주기를 외부에서 관리함으로써 생기는 메모리 효율성에 대한 문제와 그에 따라 늘어나는 코드의 복잡성이다.  </p>
<br />  

<h3 id="di-container">DI Container</h3>
<p>외부에서 생명주기를 책임지다 보면 같은 객체를 여러번 생성하는 문제가 비일비재할 것이다.<br>뿐만 아니라 DI를 직접구현하게 되면서 생기는 여러 보일러플레이트 코드들이 존재한다.<br>시스템이 커지면 커질수록 위 문제에 대한 복잡성이 높아지는데 이를 해결해주는 것이 DI Container 프레임워크다.<br>이 프레임워크는 <strong>객체 생성 책임</strong>과 <strong>객체 주입</strong>의 책임을 가지며 위의 문제점들을 해결해준다.<br>DI Container 내부에서 주입되는 객체들은 싱글톤 객체로 동작하여 메모리 효율을 줄여준다.  </p>
<br />  

<h3 id="참고">참고</h3>
<ul>
<li><a href="https://wbluke.tistory.com/9">IoC랑 DI가 그래서 도대체 뭔가요?</a></li>
<li><a href="https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f">[DI] Dependency Injection 이란?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 훝어보기]]></title>
            <link>https://velog.io/@rokwon_k/AI-%ED%9B%9D%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@rokwon_k/AI-%ED%9B%9D%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 19 Sep 2022 05:46:52 GMT</pubDate>
            <description><![CDATA[<h1 id="ai-란">AI 란</h1>
<p>Artifical Intelligence, 사람의 지능을 모방하여 사람이 하는 것과 같이 복잡한 일을 할 수 있는 기계를 만드는 것.</p>
<p>이러한 인공지능을 구현하는 방법은 크게 <strong>기계학습</strong>(머신러닝), <strong>딥러닝</strong>이 있다.</p>
<br />

<h2 id="기계학습머신러닝">기계학습(머신러닝)</h2>
<p>컴퓨터가 명시적으로 프로그램되지 않고 <strong>컴퓨터가 학습할 수 있도록 하는 것</strong></p>
<p>프로그램에서는 <code>입력과 조건을 주어 명시적으로 동작(정답 출력)</code>시킨다.
하지만 머신러닝은 <code>입력을 주고 정답을 알려준 다음 컴퓨터가 조건을 찾게한다</code> </p>
<blockquote>
<p>ex) 고양이 사진과 이것이 고양이라는 것을 알려주고 컴퓨터는 고양이가 되는 조건을 찾는다.
마치 어린아이에게 사진을 보여주며 ‘이건 고양이야’라고 알려주는 것 같다</p>
</blockquote>
<p>여러 고양이 사진을 보면서 조건을 찾으며 학습해간다. 그리고 새로운 고양이 사진을 보고 누적된 조건을 통해 고양이 사진이라는 걸 유추할 수 있다.</p>
<h3 id="기계학습의-유형">기계학습의 유형</h3>
<p>💡 <strong>지도학습(Supervised)</strong></p>
<ul>
<li>입력과 결과값(정답)을 이용한 학습</li>
<li>분류(classification)와 회귀(regression)의 방법이 있음</li>
<li>학습모델 - SVM, Decision Tree, KNN, 선형/로지스틱 회귀</li>
<li><code>정답이 있는 것</code> 을 학습시키기 ex) 이 사진이 고양이냐 아니냐</li>
</ul>
<p>💡 <strong>비지도학습(Unsupervised)</strong></p>
<ul>
<li>입력만을 이용한 학습(정답인가 오답인가 답이 없는 데이터들을 자동으로 구집, 규칙을 스스로 발견.)</li>
<li>군집화(clustering), 압축(compression)</li>
<li>학습모델 - k-means 클러스터링</li>
<li><code>정답이 없고 추측 가능한 것</code> 을 학습시키기 ex) SNS에서 이 사람과 이 사람을 알고 있으니 이 사람도 알거야! 같은 추천시스템?</li>
</ul>
<p><strong>💡 강화학습(Reinforcement)</strong></p>
<ul>
<li>결과값 대신 리워드가 주어짐, 실패와 성공의 과정을 반복하여 학습해나가는 방식. 지속적인 시행착오를 통해 성장</li>
<li>Action Selection, Policy Learning</li>
<li>학습모델 - MDP(Markov Decision Process)</li>
<li><code>해당 상황에서 최적화된 행동</code> 현재의 상태에서 어떤 행동을 취하는 것이 최적인지를 학습(행동을 취할 때마다 보상이 주어짐. 이런 보상을 최대화 하는 방향으로 학습이 진행됨.) ex) 기계 팔에게 탁구를 가르치는 것. 어떻게 들어오면 어떻게 치는게 가장 최적화된 방법인지</li>
</ul>
<h3 id="기계학습의-결과">기계학습의 결과</h3>
<p>사람이 하는 학습의 결과 두뇌에 축적됨. 
머신러닝의 결과는 가중치(파라미터) 값의 형태로 축적된다
<br /></p>
<hr>
<h2 id="딥러닝">딥러닝</h2>
<p>인공신경망(Artificial Neural Network)의 한 종류
인간의 뇌는 1000억 개가 넘는 신경세포(뉴련)이 스냅스를 통해 병렬적으로 연결되어 있음. 인경신경망 뉴런 모델은 생물학적 뉴런을 수학적으로 모델링한 것. </p>
<p>인간이 여러개의 뉴런으로부터 입력값을 받아서 세포체에 저장 용량을 넘어서면 외부로 출력값을 내보내는 것처럼 인공신경망은 입력값을 받아서 일정 수준이 넘어서면 활성화되어 출력값을 내보낸다.</p>
<p>인공신경망은 이런 인공 뉴련들을 여러개 쌓아만든다. 여러 뉴러들이 모여 하나의 Layer가 생기고 여러 Layer들로 구성된 것이 인공신경망에서 다층 퍼셉트론(MLP, Multi-Layer Perception)이라고 함</p>
<blockquote>
<p><strong>💡 다층 퍼셉트론의 한계점
-</strong> Layer가 깊어질수록 역전파 학습과정에서 데이터가 사라져 학습이 잘되지 않는 ‘사라지는 경사도(Vanishing Gradient)’문제</p>
</blockquote>
<ul>
<li>새로운 사실을 추론하는 것, 새로운 데이터를 처리하는 것을 잘 하지 못함<blockquote>
</blockquote>
</li>
</ul>
<blockquote>
<p><strong>💡 인공신경망(다층 퍼셉트론) 해결</strong>
2000년이 넘어 신경망 학습시 사전(Pretrainig)학습을 함으로써 Vanishing Gradient 문제 해결
새로운 데이터를 잘 처리 못하는 문제는 고의로 데이터를 누락(dropout)시키는 방법을 적용</p>
</blockquote>
<p>위와 같은 한계를 뛰어넘은 인공신경망을 ‘딥러닝’이라 부름(리브랜딩)</p>
<h3 id="딥러닝의-유행">딥러닝의 유행</h3>
<ol>
<li>인공신경망의 한계를 극복할 수 있었던 알고리즘의 개발(위와 해결법)</li>
<li>정보화 시대의 흐름으로 신경망 학습에 필요한 막대한 학습데이터가 축적</li>
<li>신경망을 이용한 학습,계산에 적합한 하드웨어의 발전(GPU 등)</li>
</ol>
<br />

<h3 id="아래-참고자료를-요약한-내용입니다">아래 참고자료를 요약한 내용입니다</h3>
<p><a href="https://brunch.co.kr/@gdhan/10">https://brunch.co.kr/@gdhan/10</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Serverless Framework를 이용해 Slack으로 Error log 받기]]></title>
            <link>https://velog.io/@rokwon_k/Serverless-Framework%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-Slack%EC%9C%BC%EB%A1%9C-Error-log-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@rokwon_k/Serverless-Framework%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-Slack%EC%9C%BC%EB%A1%9C-Error-log-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Wed, 14 Sep 2022 13:47:09 GMT</pubDate>
            <description><![CDATA[<p>현재 진행중인 프로젝트에서 실서비스를 운영중 서비스 이용에 큰 문제를 주는 에러가 발생했다. 문제는 이틀동안 해당문제가 발생하는지 모르고 있다가 한 고객의 문의로  알게 되었다.(정말 감사합니다😂ㅠㅠ) AWS CloudWatch에서 로그를 확인하니 이틀간 상당수의 유저들이 해당 에러 때문에 불편을 겪은 것을 확인하였다ㅜ
적은 인원으로 기능 개발에 집중하다보니 운영에 신경을 못썼고 그제서야 심각성을 인지하고 빠른 오류 인지를 위해 Error log를 Slack으로 알림받기로 결정하였다.</p>
<br />

<h2 id="어떤-도구들을-사용할까고민">어떤 도구들을 사용할까(고민)</h2>
<h3 id="🤔-aws-lambda를-이용하기">🤔 AWS lambda를 이용하기</h3>
<p>AWS lambda란 FaaS(Function as a Service)로 서비리스 컴퓨팅 서비스이다.
오류가 뜨면 서버에서 에러로그를 슬랙으로 보내주면 되지 않는가?라고 생각할 수 있다. 
그렇게 되면 API를 추가할 때마다 에러처리로직에 Slack으로 알림을 주는 로직을 추가해줘야하는데 이런 귀찮고 실수가 잦은 부분을 따로 분리하여 <strong>CloudWatch Log에서 어떤 에러든 확인되면 lambda 함수를 실행해 로그를 Slack에 뿌려주도록 설계하였다</strong></p>
<blockquote>
<p><strong>💡 서버리스란</strong>
서버를 따로 관리하지 않고 클라우드에게 서버의 관리를 맡기고 오직 로직(코드)에만 집중할 수 있게해주는 서비스이다. 서버리스의 한 갈래인 FaaS의 경우 개발자가 코드를 함수단위로 작성하고 배포하면 AWS에서 개발자가 직접 관리하지 않아도 되는 서버에서 실행된다.</p>
</blockquote>
<br />

<h3 id="🤔-serverless-framework-이용한-설계">🤔 Serverless Framework 이용한 설계</h3>
<p>클라우드는 매우 강력한 기능들을 제공하지만 동시에 사용하기 매우 어렵고 복잡하다. 각 리소스마다 정책설정하고 리소스끼리 연결시키는 등 AWS console를 이용하면
직관성과 가독성이 다소 떨어지는 경향이 있다.</p>
<p>복잡한 인프라 설계를 보다 편리하게 관리하고 직관적으로 이해할 수 있도록 많은 IaC(Infra as Code)도구들이 생겨났고 Serverless로 아키텍쳐를 설계하는 부분에서는 <code>Serverless Framework</code>가 잘 만들어져 있으며 이 도구를 이용하기로 결정했다.</p>
<p>내가 작성할 시스템의 젠체적인 흐름은 아래와 같고 흐름에 대한 설계를 Serverless Framework를 이용해서 작성할 예정이다.</p>
<ol>
<li>CloudWatch에서 로그이벤트 발생</li>
<li>Log가 Error일 경우 lambda 함수를 실행</li>
<li>lambda 함수에서 slack으로 로그 내용을 전송하는 구조이다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/1249a35e-2197-463c-821b-3801034ffd2b/image.png" alt=""></p>
<p><a href="https://www.serverless.com/cloud/docs">Serverless Framework 바로가기</a></p>
<blockquote>
<p><strong>💡 Iac(Infra as Code)</strong>
인프라 설계를 YML과 같은 설정파일을 이용해서 코드로써 관리하는 도구들이다.
각 자원별로 정책설정, 자원끼리 연동 등 AWS console에서 하나하나 마우스를 클릭하면서 했던 일들, cli를 이용해서 명령어를 쳐야했던 부분들을 하나의 파일로 관리하고 실행시킬 수 있다.</p>
</blockquote>
<br />
<hr>
<br />

<h2 id="본격적인-error-log-시스템-구축">본격적인 Error Log 시스템 구축</h2>
<h3 id="1️⃣-serverless-framework-설계-환경-구축">1️⃣ Serverless Framework 설계 환경 구축</h3>
<p>먼저 Serverless Framework 설치한다. 필자는 npm을 사용하여 설치하였다.</p>
<pre><code class="language-shell">npm install -g serverless</code></pre>
<br />

<p>프로그래밍으로 인프라 설계를 하기 위해서는 AWS에서 해당 액세스를 허락 받은 사용자만 가능하다. 그러므로 AWS IAM에서 사용자를 추가해준다.  (<em>참고 - Serverless Framework는 배포시 AWS Cloudformation 코드로 변환되어 배포된다.</em>)</p>
<p>(IAM - 사용자 - 사용자 추가)
<img src="https://velog.velcdn.com/images/rokwon_k/post/8ee10d88-18a4-466f-84a4-057c698a004b/image.png" alt=""></p>
<p>(꼭 AWS 액세스 유형은 프로그래밍 방식 액세스를 선택하여야 한다!)
<img src="https://velog.velcdn.com/images/rokwon_k/post/de972288-c3ed-4135-8290-ccceaa07d045/image.png" alt=""></p>
<p>(권한설정은 AWS 리소스 제어권한을 의미하는데 일단 모든 권한을 허락하였다)
<img src="https://velog.velcdn.com/images/rokwon_k/post/5dc345c5-2c03-4239-8ab0-aa57098baa2f/image.png" alt=""></p>
<p>(마지막에 나오는 <code>액세스 키 ID</code>와 <code>비밀 액세스 키</code>는 따로 저장해두자)
<img src="https://velog.velcdn.com/images/rokwon_k/post/2fe51156-c1bc-46d2-b212-25e3ee88c9cb/image.png" alt=""></p>
<p>권한설정을 끝마쳤으니 해당 IAM 유저로 로그인한다.(serverless framework를 설치하였다면 <code>serverless</code>명령어를 사용할 수 있으며 <code>sls</code>로도 사용가능하다</p>
<pre><code class="language-shell">serverless config credentials \
  --provider aws \
  --key {액세스 키 ID} \
  --secret {비밀 액세스 키} \</code></pre>
<br />


<h3 id="2️⃣-serverless-framework-개발-환경-구축">2️⃣ Serverless Framework 개발 환경 구축</h3>
<p>AWS를 코드로 제어할 수 있는 환경설정을 마쳤다. 이번엔 람다코드를 작성 하기위한 개발환경을 구축해보자. 람다함수는 파이썬이나 Go, Node.js 등으로 작성할 수 있다. 필자는 Node.js + typescript 환경을 이용하였다. 환경을 만들기 위해 0부터 모든 코드를 짤 수 있지만 템플릿을 이용해 기본 환경을 구축해보자.</p>
<p>아래 명령어를 입력시 serverless framework에서 제공해주는 템플릿들을 볼 수 있다.</p>
<pre><code class="language-shell">sls create --help</code></pre>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/3165da88-4395-44cc-86bc-01e811d57e8e/image.png" alt=""></p>
<p>aws-nodejs 템플릿을 이용해 프로젝트를 만든다. aws-nodejs-typescript 템플릿을 이용해 처음부터 typescript가 적용된 프로젝트를 만들 수 있지만 typescript는 직접 설정해 보기로 한다.</p>
<pre><code class="language-shell">sls create -t aws-nodejs -p {프로젝트 이름}</code></pre>
<br />


<p>루트 디렉터리에 <code>serverless.yml</code> 파일이 있는데 해당 파일이 바로 설계 코드를 작성하는 파일이다! 이어서 typescript를 적용해보자
<a href="https://www.serverless.com/plugins/serverless-plugin-typescript">참고자료</a></p>
<pre><code class="language-shell">npm install -D serverless-plugin-typescript typescript</code></pre>
<br />

<p>위 명령어로 설치 후 <code>serverless.yml</code> 파일에 아래 명령어를 입력해준다.</p>
<pre><code class="language-yaml">plugins:
  - serverless-plugin-typescript</code></pre>
<br />

<p>추가적으로 애초에 nodejs 템플릿으로 만들었기 때문에 몇몇 필수 라이브러리들의 타입스크립트 버전이 설치가 안되어있을 것이다. 고로 원할한 타입스크립트 사용을 위하여 몇몇 가지를 설치해준다.</p>
<pre><code class="language-shell">npm install -D @types/aws-lambda @types/node</code></pre>
<br />

<p>마지막으로 루트에 tsconfig.json파일을 만들고 typescript 환경설정을 해준다.
<a href="https://geonlee.tistory.com/214">tsconfig.json 옵션들에 대한 참고자료 바로가기</a></p>
<pre><code class="language-json">{
    &quot;compilerOptions&quot;: {
        &quot;module&quot;: &quot;commonjs&quot;,
        &quot;target&quot;: &quot;ES2016&quot;,
        &quot;outDir&quot;: &quot;.build&quot;,
        &quot;noImplicitAny&quot;: true,
        &quot;strictPropertyInitialization&quot;: true,
        &quot;moduleResolution&quot;: &quot;node&quot;,
        &quot;sourceMap&quot;: true,
        &quot;rootDir&quot;: &quot;./&quot;,
        &quot;experimentalDecorators&quot;: true,
        &quot;emitDecoratorMetadata&quot;: true,
        &quot;strictNullChecks&quot;: true,
        &quot;resolveJsonModule&quot;: true,
        &quot;strict&quot;: true,
        &quot;allowJs&quot;: true,
        &quot;allowSyntheticDefaultImports&quot;: true,
        &quot;lib&quot;: [
            &quot;es2015&quot;
        ],
        &quot;typeRoots&quot;: [
            &quot;@types&quot;,
            &quot;node_modules/@types&quot;
        ]
    }
}</code></pre>
<p>이로써 기본적인 환경설정 구축은 끝났지만 개발을 하면서 필요한 몇몇 라이브러리가 있다. 그때그때 설치하면서 사용하기로 하자</p>
<br />

<h3 id="3️⃣-cloudwatch-logevent-구독과-handler-설정">3️⃣ CloudWatch LogEvent 구독과 handler 설정</h3>
<p><code>serverless.yml</code>에서 CloudWatch의 LogEvent 중 Error가 발생하면 지정한 람다함수를 실행하도록 설계해보자.</p>
<pre><code class="language-yml"># 이름
service: adevspoon-pipeline

# serverless framwork 버전
frameworkVersion: &#39;3&#39;

# serverless framework 플러그인들 정의
plugins:
  - serverless-plugin-typescript

# 인프라와 관련된 정의 및 환경설정들
provider:
  name: aws
  runtime: nodejs14.x
  region: ap-northeast-2
  timeout: 30
  memorySize: 128
  stage: ${opt:stage, &#39;dev&#39;}

# 람다함수들의 정와와 실행될 이벤트 정리
functions:
  myCloudWatchLog:
    handler: {파일이름}.{export한 함수 명}
    events:
      - cloudwatchLog: 
          logGroup: &#39;{logGroup 이름}&#39;
          filter: &#39;?ERROR ?Exception&#39;
      - cloudwatchLog: 
          logGroup: &#39;{logGroup 이름}&#39;
          filter: &#39;?ERROR ?Exception&#39;</code></pre>
<br />

<p>하나씩 살펴보자.
가장 상단의 두 섹션은 프로젝트 디렉터리의 이름과 framwork 버전을 뜻한다.</p>
<pre><code class="language-yml"># 이름
service: adevspoon-pipeline

# serverless framwork 버전
frameworkVersion: &#39;3&#39;</code></pre>
<br />

<p>이 부분은 serverelss framework의 플러그인들을 추가하는 섹션이다.
이미 설치한 typscript 말고도 앞으로 2가지의 플러그인들을 설치하고 이 섹션에 추가할 예정이다.</p>
<pre><code class="language-yml"># serverless framework 플러그인들 정의
plugins:
  - serverless-plugin-typescript</code></pre>
<br />

<p>provider 부분은 어떤 클라우드 제공업체를 사용할 것인가 등 사용하는 인프라 환경에 대해 정의해 놓을 수 있다. 코드를 실행할 환경, aws region 등을 설정해놓았다.
맨 아래 <code>stage</code>는 배포할 환경을 뜻한다. 
<code>${opt:stage, &#39;dev&#39;}</code>를 설명하자면 구축한 환경을 배포할때 <code>sls deploy</code>라는 명령어를 통해 aws에 배포하게 되는데
이때 아무런 옵션을 주지 않으면 기본적으로 dev로 배포한다는 뜻이고 <code>sls deploy --stage prod</code>와 같이 --stage 옵션을 추가하면 배포환경을 명시적으로 지정할 수 있다. stage를 이용해서 dev나 prod 등 환경을 나누어 개발할 수 있다.</p>
<pre><code class="language-yml"># 인프라와 관련된 정의 및 환경설정들
provider:
  name: aws
  runtime: nodejs14.x
  region: ap-northeast-2
  timeout: 30
  memorySize: 128
  stage: ${opt:stage, &#39;dev&#39;}</code></pre>
<br />

<p>이 섹션이 메인이다. <code>functions</code>란 람다함수를 정의해 놓는 섹션이다.
<code>myCloudWatchLog</code> - 람다함수의 이름
<code>handler</code> - 람다 실행시 동작할 함수코드
<code>events</code> - 람다를 실행시킬 이벤트
작성된 코드의 events 파트를 보면 <strong>cloudwatchLog에서 지정한 LogGroup에서 발생하는 이벤트 중 Error거나 Exception을 발생했을때</strong>라고 정의되어있다.
정의된 event가 부합되었을때 handler에 작성된 함수가 실행된다
events에는 <code>-</code>를 이용해서 다수의 이벤트를 구독하도록 작성이 가능하다.</p>
<pre><code class="language-yaml"># 람다함수들의 정와와 실행될 이벤트 정리
functions:
  myCloudWatchLog:
    handler: {파일이름}.{export한 함수 명}
    events:
      - cloudwatchLog: 
          logGroup: &#39;{logGroup 이름}&#39;
          filter: &#39;?ERROR ?Exception&#39;
      - cloudwatchLog: 
          logGroup: &#39;{logGroup 이름}&#39;
          filter: &#39;?ERROR ?Exception&#39;</code></pre>
<br />

<h3 id="4️⃣-slack-설정">4️⃣ Slack 설정</h3>
<p>람다함수를 작성하기 이전에 Slack으로 알림을 보내기 위해선 Slack에 앱을 추가하고 해당 앱에 권한 설정이 필요하다.</p>
<p><a href="https://api.slack.com/apps">슬랙 api</a>에 들어가서 &#39;Create New App&#39; 클릭해주자(클릭 후 from scratch 선택, 앱 네임, workspace를 지정해준다)
<img src="https://velog.velcdn.com/images/rokwon_k/post/1358c9f9-60f7-4349-97de-eed0f3b5fb69/image.png" alt=""></p>
<p>아래와 같은 페이지가 나올텐데 Bots를 선택해준다</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/3cce6e86-2efa-4836-8c51-e8b5ac3c39e3/image.png" alt=""></p>
<p>좌측 메뉴의 OAuth &amp; Permissions 선택 - Scopes의 Bot Token Scopes에서 write에 관한 권한을 추가해주자(잘 알고 계시다면 필요한 것만 골라서!)
<img src="https://velog.velcdn.com/images/rokwon_k/post/442a2adc-9ed8-4698-9931-7993003156cc/image.png" alt=""></p>
<p>좌측 메뉴의 base information을 선택 - Install your app을 클릭하여 workspace에 앱을 추가해주자
<img src="https://velog.velcdn.com/images/rokwon_k/post/95100bb2-ad6b-40c5-aeb7-66bd948d264e/image.png" alt=""></p>
<p>다시 OAuth &amp; Permissions로 돌아가면 Token이 생성될 것인데 복사 후 잘 간직하자 Slack API를 사용하기 위해서 꼭 필요한 토큰이다
<img src="https://velog.velcdn.com/images/rokwon_k/post/b8f101ce-c6f2-4bf8-b35f-a93317a80a53/image.png" alt=""></p>
<p>마지막으로 슬랙의 workspace에서 원하는 채널에 만든 앱을 초대해준다
<img src="https://velog.velcdn.com/images/rokwon_k/post/ab8a54b5-2665-4a50-94b9-fd323d6da511/image.png" alt=""></p>
<br />

<h3 id="5️⃣-lambda-핸들러-작성">5️⃣ lambda 핸들러 작성</h3>
<p>함수 내부에서는 받은 이벤트를 slack으로 noti를 해주는 코드를 작성할 것이다.</p>
<p>우선 기본적으로 작성되는 handler.ts(handler.js 파일 확장자 변경)을 열고 아래와 같이 작성했다.</p>
<pre><code class="language-ts">export { default as sendErrorLogToSlack } from &#39;./src/sendErrorLogToSlack&#39;;</code></pre>
<br />

<p>다음으로 src라는 디렉터리를 만들고 내부에 <code>sendErrorLogToSlack.ts</code>를 만들어 준다.
<br /></p>
<p>slack에서 제공해주는 REST API를 사용해도 문제없지만 라이브러리를 사용해서 간편하게 사용하기로 했다.</p>
<pre><code class="language-shell">npm install @slack/web-api</code></pre>
<br />

<p>마지막으로 위에서 발급한 Slack Token, 메시지를 보낼 Slack내 채널ID를 숨겨주기 위해서 <code>.env</code> 파일을 만들어 주고 서버리스 내에서 <code>.env</code> 파일을 사용하기 위해서 플러그인으로 <code>serverless-dotenv-plugin</code>를 설치해 준다.</p>
<pre><code class="language-shell">npm install -D serverless-dotenv-plugin</code></pre>
<pre><code class="language-yml"># serverless.yml 파일에도 작성하기
plugins:
  - serverless-plugin-typescript
  - serverless-dotenv-plugin</code></pre>
<br />



<p>아래는 <code>sendErrorLogToSlack.ts</code> 파일의 코드이다. CloudWatch의 로그를 json으로 디코딩 -&gt; 메세지 셋팅 -&gt; 슬랙 메시지 전송순으로 작성하였다. </p>
<pre><code class="language-ts">import { ChatPostMessageResponse, ErrorCode, WebClient } from &#39;@slack/web-api&#39;;
import {
  CloudWatchLogsDecodedData,
  CloudWatchLogsEvent,
  CloudWatchLogsEventData,
  CloudWatchLogsHandler,
} from &#39;aws-lambda&#39;
import * as zlib from &#39;zlib&#39;

const slackClient = new WebClient(process.env.SLACK_BOT_TOKEN);

const sendErrorLogToSlack = async (event: CloudWatchLogsEvent) =&gt; {
  // CloudWatch LogEvent Decoding
  const compressedData: CloudWatchLogsEventData = event.awslogs;
  if (!compressedData) return
  const payload = Buffer.from(compressedData.data, &#39;base64&#39;);
  const decodedData = JSON.parse(zlib.unzipSync(payload).toString()) as CloudWatchLogsDecodedData;
  const errorMessage = decodedData.logEvents.map((e) =&gt; e.message).join(&quot; &amp; &quot;)

  // Slack Message Setting
  const slackMessage = `
    ❓❓Error 
    ▶️ Log Date: ${(new Date()).toISOString().replace(&quot;T&quot;, &quot; &quot;).replace(/\..*/, &#39;&#39;)}
    ▶️ Log Group: ${decodedData.logGroup}
    ▶️ Log Message: ${errorMessage}
  `;

  // Notify to Slack
  try {
    const messageResponse: ChatPostMessageResponse = await slackClient.chat.postMessage({
      text: slackMessage,
      channel: `${process.env.SLACK_ERROR_LOG_CHANNEL_ID}`
    });
  } catch (error) {
    console.log(`Notify to Slack Error ${error}`);
  }
};

export default sendErrorLogToSlack;</code></pre>
<br />

<h3 id="6️⃣-배포">6️⃣ 배포</h3>
<p>아래 명령어만 입력하면 배포완료!</p>
<pre><code class="language-shell">sls deploy</code></pre>
<p>개발환경 별로 다르게 배포를 하고 싶다면 <code>--stage</code> 옵션을 사용하면 된다.</p>
<pre><code class="language-shell">sls deploy --stage prod</code></pre>
<br />
<hr>
<br />

<h2 id="더-생각해보아야-할-것">더 생각해보아야 할 것</h2>
<h3 id="🤔-aws-sns-이용해-이벤트-받기">🤔 AWS SNS 이용해 이벤트 받기?</h3>
<p>여러 레퍼런스를 찾던 중 내가 작성한 방법에서 Cloudwatch와 lambda 사이에 AWS SNS(알림을 주는 서비스)를 사용하는 예시들이 많았다. 굳이 SNS를 왜 사용할까 곰곰히 생각해보았는데 SNS가 하나의 멀티포트의 역할을 해주는 것이 아닐까 싶다. 내가 구현한 방식은 1:1 방식으로는 문제없으나 혹여나 1:N 방식으로 알림을 주어야할때 번거로운 코드들이 추가되어야 할 것 같다.
(또 다른 이유를 아시는 분들은 댓글로 알려주시면 감사하겠습니다!)</p>
<h3 id="🤔-cloudwatch-log-group-전부-받아오기">🤔 CloudWatch Log group 전부 받아오기</h3>
<p>여전히 해결하지 못한 문제이다. 여러 개의 Log group을 모두 한 handler에 등록시키고 싶은데 yml파일의 logGroup 설정에서 와일드카드(*)의 사용이 금지되어있다. 일단은 할 수 없이 모든 logGroup을 하드코딩해 놨지만 마음에 들지는 않는다ㅋㅋ..
(방법을 아시는 분들은 댓글로 알려주시면 감사하겠습니다!)</p>
<h3 id="🤔-nodejs-buffer-api">🤔 Node.js Buffer API</h3>
<p>json으로 Decoding하는 과정에서 Node.js의 내장 함수인 Buffer를 사용하였다. 정확히 알고 사용한 부분이 아니므로 공부를 할 필요성이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker의 등장과 특징]]></title>
            <link>https://velog.io/@rokwon_k/%EB%8F%84%EC%BB%A4%EC%9D%98-%EB%93%B1%EC%9E%A5%EA%B3%BC-%ED%8A%B9%EC%A7%95</link>
            <guid>https://velog.io/@rokwon_k/%EB%8F%84%EC%BB%A4%EC%9D%98-%EB%93%B1%EC%9E%A5%EA%B3%BC-%ED%8A%B9%EC%A7%95</guid>
            <pubDate>Mon, 12 Sep 2022 10:31:11 GMT</pubDate>
            <description><![CDATA[<p>서버관리의 측면에서 월등히 높은 효율성을 자랑한다는 도커, 공부해보자!
그 등장배경을 알면 쉽게 이해할 수 있으니 배경부터 알아보자</p>
<br />

<h2 id="docker의-등장-배경">Docker의 등장 배경</h2>
<h3 id="1️⃣-문서화의-문제점">1️⃣ 문서화의 문제점</h3>
<p>아주 오래전 서버에 대한 관리는 문서를 통해 이루어졌다.
어떤 OS를 사용하고 어떤 라이브러리를 이용한다 등을 한글 파일과 같이 문서를 통해 배우고 사용하였다.
이럴 경우 실제 서버환경과 테스트환경, 로컬환경의 OS등이 다를 가능성이 크며, 명령어 하나라도 실수하면 제대로 돌아가지 않는 불상사가 일어났다.</p>
<blockquote>
<p><strong>💡 해결법</strong>
<strong>설정파일의 등장!</strong> 환경 설정에 필요한 것들을 코드로써 관리하고 이 파일을 실행함으로써 필요한 환경 셋팅을 해준다!</p>
</blockquote>
<ul>
<li>사람의 손을 거침으로써 나는 에러 해결</li>
<li>환경설정에 드는 시간 절약</li>
</ul>
<br />

<h3 id="2️⃣-설정파일의-문제점">2️⃣ 설정파일의 문제점</h3>
<p>설정파일에 적힌 내용을 이해하기에 러닝커브가 높음.
한 서버 내에서 같은 프로그램을 다른 버전(환경)으로 여러개 배포하기가 매우 복잡함</p>
<blockquote>
<p><strong>💡 해결법</strong>
<strong>가상머신의 등장!</strong> 하나의 물리 자원에 가상화층을 만들어 또 다른 OS를 동작하게 함으로써 한 서버내에서 다른 환경으로 여러 프로그램을 실행시킬 수 있음.</p>
</blockquote>
<br />

<h3 id="3️⃣-가상머신의-문제점">3️⃣ 가상머신의 문제점</h3>
<p>가상머신을 처음부터 셋팅하기가 어려움.
처음 만든 사람이 셋팅 문서를 결국 작성해야함(기존의 문제로 회귀), 공유하기가 어려움(용량이 너무 커서 어디에 올릴 수도 없음)
OS를 통하기 때문에 속도가 느려지는 단점이 생긴다.</p>
<blockquote>
<p><strong>💡 해결법</strong>
자원의 격리 - 리눅스 커널을 직접 건드려 자원을 격리시키는 방법</p>
</blockquote>
<ul>
<li>프로세스를 겹치지 않게 격리한다. 뿐만아니라 파일/디렉터리도 분리하여 사용가능</li>
<li>CPU, Memory, I/O를 그룹별로 제한시킬 수 있음</li>
<li>리눅스 자체의 기능을 활용한 것이므로 빠르고 효율적임</li>
</ul>
<br />

<h3 id="4️⃣-자원격리의-문제점">4️⃣ 자원격리의 문제점</h3>
<p>리눅스 커널을 직접 다루는 기술은 고난이도. 글로벌 IT기업에서만 사용가능한 수준임. 
이러한 어려운 자원격리 기술을 손쉽게 사용가능하게 만든 것이 바로 컨테이너 기술(도커)이다.</p>
<br />
<hr>
<br />

<h2 id="docker-알아보기">Docker 알아보기</h2>
<h3 id="🧐-docker-vs-vm">🧐 Docker vs VM</h3>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/64c0aeba-f57a-41eb-b6a0-c35dd4a358be/image.png" alt=""></p>
<p>위에 보이는 사진처럼 VM은 Hypervisor의 IO처리와 Guest OS로 인하여 오버헤드가 발생한다.(Guest OS로 인한 메모리, Guest OS의 IO처리로 인한 속도저하)</p>
<ul>
<li>Docker는 가상OS가 아니라 <strong>가상 프로세스(컨테이너) 단위</strong>로 격리하며 OS를 공유한다.</li>
<li>기존 하드웨어의 성능을 그대로 사용함.</li>
<li>하나의 OS로 소통하기 때문에 가볍고 빠름</li>
</ul>
<br />

<h3 id="🧐-docker-특징-장점">🧐 Docker 특징, 장점</h3>
<p><strong>확정성과 이식성이 좋음</strong></p>
<ul>
<li>도커만 설치되어 있으면 어디서든 컨테이너 실행 가능(but, 기본적으로 Linux를 제공하기 때문에 Window나 MacOS에서는 가상머신에 설치됨. 하지만 걱정하지마라 우리는 클라우드를 사용하니까)<br />

</li>
</ul>
<p><strong>표준성</strong></p>
<ul>
<li>각 언어별(ruby, nodejs) 등으로 만든 서비스들의 배포 방식은 모두 다르지만 도커는 <strong>컨테이너라는 표준으로 서버를 배포</strong> 하므로 배포과정이 동일 - (사용하는 라이브러리 다운 등 설정파일만 적어놓으면 나머지 시스템상 문제는 도커 엔진이 다 처리해주니 걱정마시라구)</li>
<li>도커로 돌리면  로컬 테스트환경(맥이든 윈도우든 문제없구), 실제 환경(로컬 환경과 달라도)이 달라도 모두 문제없이 실행됨.<br />

</li>
</ul>
<p><strong>이미지(container image)를 만들기 쉬움</strong></p>
<ul>
<li>컨테이너를 생성하기 위한 것.</li>
<li>이미지를 만드는 과정이 필요함(Dockerfile을 이용하여 이미지 만들기, 다른사람이 만든 이미지 가져오기)</li>
<li>환경을 교체, 업데이트하기 쉬움</li>
<li>컨테이너 : 이미지를 이용해 실행한 <strong>격리되어 있는 프로세스</strong><br />

</li>
</ul>
<p><strong>도커 호스트</strong></p>
<ul>
<li>컨테이너가 설치되어있는 호스트(호스트에 여러개의 컨테이너가 만들어짐)</li>
<li>컨테이너와 호스트는 독립적인 환경을 가짐(독립적인 포트와 파일시스템)</li>
<li>그러므로 호스트의 포트와 컨테이너 포트를 연결시켜 호스트 포트로 들어오는 것들을 연결된 컨테이너로 전송되게 해야함(포트포워딩 기술)<br />

</li>
</ul>
<p><strong>설정관리</strong></p>
<ul>
<li>보통 환경변수로 제어함. 환경변수를 바꿔서 실행하면 내부에 있는 툴들을 환경변수에 맞춰 바꿔줌.<br />

</li>
</ul>
<p><strong>자원관리</strong></p>
<ul>
<li>컨테이너를 삭제하면 모든 데이터가 초기화됨.</li>
<li>그러므로 업로드 파일을 S3같은 별도의 저장소가 필요함.</li>
<li>세션이나 캐시는 radis와 같은 외부에서 관리하는게 좋음.<br />

</li>
</ul>
<p><strong>업데이트 전 테스트하기 편함</strong></p>
<ul>
<li>새로운 컨테이너 환경으로 따로 생성하고 테스트 용이<br />

</li>
</ul>
<p><strong>결론적으로 서버를 효율적으로 사용,관리할 수 있음</strong></p>
<ul>
<li>컴퓨터 리소스를 제대로 활용가능(다른환경 프로그램들을 속도,메모리 효율적으로 실행가능)</li>
<li>개발, 테스트, 프로덕트 등의 개발환경을 손쉽게 나누고 구축할 수 있음.</li>
</ul>
<br />

<h3 id="🧐-docker-단점">🧐 Docker 단점</h3>
<ul>
<li>리눅스 운영체제만 사용가능</li>
<li>호스트에 문제가 생기면 연관된 모든 컨테이너가 영향을 받음</li>
<li>컨테이너를 하나만 사용하거나 서버가 고정되어 있을 경우에는 오히려 역효과</li>
</ul>
<br />

<h3 id="🧐-컨테이너-오케스트레이션">🧐 컨테이너 오케스트레이션</h3>
<p>간단히 말해 여러 서버를 가지고 있는 대규모 서비스에서 컨테이너 기술을 활용하여 서비스 질을 향상 시키는 것. 대표적으로 쿠버네티스, 도커 스웜 등이 있음.</p>
<p><strong>도커</strong>는 하나의 서버에 여러개의 서비스를 관리하는 것이라고 한다면
<strong>쿠버네티스</strong>는 여러개의 서버와 여러개의 서비스를 관리하는 것(대규모 프로젝트에서 용이), 여러개의 도커를 돌리고 있는 것.</p>
<ul>
<li>스케줄링<ul>
<li>컨테이너를 적당한 서버에 배포함.(여러 대 중에서 할일 없는 서버에 배포 or 차례대로 or 랜덤하게)</li>
<li>컨테이너 수가 늘면 적당히 알아서 여러 서버로 나눠서 배포해줌.</li>
<li>서버가 죽으면 실행 중이던 그 서버의 컨테이너를 다른 서버에 띄워줌</li>
</ul>
</li>
<li>클러스터링<ul>
<li>여러개의 서버를 하나의 서버처럼(가상) 묶는 기술 - 중앙에 있는 API를 통해서 모든 곳에 접근 가능</li>
<li>여러 서버로 흩어진 컨테이너도 이런 가상 네트워크를 이용해 같은 서버에 있는 것처럼 쉽게 통신함.</li>
</ul>
</li>
<li>서비스 디스커버리<ul>
<li>서비스를 찾아주는 기능</li>
<li>클러스터 환경에서 컨테이너가 어느 서버에 생성되었는지 알 수 없고 다른 서버로 이동도 할 수 있음. 컨테이너끼리의 소통을 위해선 어디에 있는지 IP와 Port같은 정보를 알아야함.
→ DNS 서버를 이용해서 도커컨테이너 이름으로 바로 키-벨류 스토리지에 접근해 정보를 저장, 및 가져올 수 있음.
자</li>
</ul>
</li>
</ul>
<br />
<hr>
<br />



<h3 id="공부자료">공부자료</h3>
<p><a href="https://www.inflearn.com/course/%EB%8F%84%EC%BB%A4-%EC%9E%85%EB%AC%B8/dashboard">인프런 - 초보를 위한 도커 안내서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Web 필수 지식 - CORS]]></title>
            <link>https://velog.io/@rokwon_k/Web-CORS</link>
            <guid>https://velog.io/@rokwon_k/Web-CORS</guid>
            <pubDate>Fri, 09 Sep 2022 10:27:55 GMT</pubDate>
            <description><![CDATA[<p>CORS(Cross-Origin Resoure Sharing) 동일한 출처가 아닐때 검사하는 정책이라고 두루뭉술하게 알고 있었는데 프로젝트 진행 중 CORS 때문에 2,3일 고생하면서 제대로 습득해야하는 지식이라는 걸 뼈저리게 깨달았다. 개념과 동작방식, 평소 궁금했던 내용들도 함께 정리해본다.</p>
<p><br /><br /></p>
<h2 id="웹의-origin출처-policy">웹의 Origin(출처) Policy</h2>
<p>먼저 <strong>Origin이 무엇인지</strong>에 대해서 명확히 알고 갈 필요가 있다.
아래의 사진은 URL의 구성요소이다. 이 URL중 <code>Protocol + Host + Path</code>을 합친 부분을 Origin한다. 이 중 단 하나라도 다르다면 다른 출처인 것이다.</p>
<p>예를 들어,
<a href="https://naver.com">https://naver.com</a> 과 <a href="http://naver.com">http://naver.com</a> 은 프로토콜이 다르므로 다른 출처이다.</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/94c5e24c-59d7-4e6d-91a5-950393b368ea/image.png" alt="Origin"></p>
<br />


<h3 id="🤔-sop---브라우저-origin-policy">🤔 SOP - 브라우저 Origin Policy</h3>
<p>웹은 크게 두가지의 Origin 정책을 가지고 있다. 그 중 첫번째가 SOP(Same-Origin Policy)이다.</p>
<p>브라우저는 <strong>서버로 요청을 보낼때 같은 출처인지 확인</strong> 하는데 이것이 SOP이다.
만약 다른 출처라면 브라우저는 다음에 설명할 CORS 정책을 따라 요청을 시도할 것이다.</p>
<p>확실히 알아야 할것은 출처를 비교하는 것은 서버의 역할이 아니고 <strong>브라우저가 하는 역할</strong>이다. 서버로 보내기전 브라우저에서 체크를 하는 것이다.</p>
<blockquote>
<p><strong>💡 IE(Internet Explorer)의 Origin 비교</strong>
위에서 설명했듯이 Orign은 <code>Protocol + Host + Port</code>이다. 하지만 IE에서 Origin을 비교할때는 Port른 비교하지 않는다. 즉, <code>Protocol + Host</code>만을 이용해서 Origin을 비교한다</p>
</blockquote>
<br />

<h3 id="🤔-cors---브라우저-origin-policy">🤔 CORS - 브라우저 Origin Policy</h3>
<p>CORS(Cross-Origin Resource Sharing)은 <strong>서버측에서 헤더를 통해서 다른 출처(웹)에서 자원에 접근할 수 있는 권한을 부여하도록 브라우저에게 알려주는 정책</strong>이다.</p>
<p>브라우저가 CORS를 확인하는 방법은 아래와 같다</p>
<blockquote>
<ol>
<li>웹에서 서버로 요청</li>
<li>브라우저에서 요청 헤더에 <code>Origin</code>을 추가로 담아서 보낸다. </li>
<li>서버에서는 보내는 응답 헤더 안에 <code>Access-Control-Allow-Origin</code> 넣어서 보낸다.</li>
<li>브라우저는 요청시 보낸 <code>Origin</code>과 응답 헤더 안에 담긴 <code>Access-Control-Allow-Origin</code>의 값을 비교한다</li>
<li>만약 <code>Access-Control-Allow-Origin</code>안에 <code>Origin</code>이 포함되지 않는다면 브라우저가 해당 응답을 버리고 <strong>CORS Policy를 위반</strong>했다는 에러를 console에 뿌려준다.</li>
</ol>
</blockquote>
<p>위 과정에서 알다시피 클라이언트-서버의 소통은 정상적으로 성공하였으므로 서버의 로그에도 정상적으로 응답했다고 찍힐 것이다. CORS를 제대로 이해하지 못하고 있다면 굉장히 골치 아파질 것이고 필자가 그러하였다.<del>(내 2일 돌려줘)</del></p>
<br />
<hr>
<br />

<h2 id="cors-동작방식-제대로-알아보기">CORS 동작방식 제대로 알아보기</h2>
<p>CORS 정책이 동작하는 방식을 조금 더 자세히 알아보겠다. 
이 파트를 정확히 숙지해야 console에서 나는 오류메시지를 보고 <strong>CORS 정책 중에서 어떤 부분을 위배되었는지</strong> 빠르게 알아차릴 수 있다. 필자는 이 부분도 제대로 숙지하지 못해서 많은 고생을 했다. 흑ㅠㅠ</p>
<p>CORS는 상황에 따라서 <strong>Preflight Request, Simple Request, Credentialed Request</strong> 이 3가지 요청방식을 사용한다.</p>
<br />

<h3 id="🤔-preflight-request">🤔 Preflight Request</h3>
<p>예비 요청이라고 부르는데 Preflight Request로 알고 있는게 오류 메시지를 보고 문제를 알아차리기 편하다. 이 요청에 대해 위배가 되면 console 메세지에 preflight response라는 단어가 들어가는 오류를 뿜어준다.</p>
<p>브라우저에서는 서버로 요청을 보낼때 두 번의 요청을 보낸다. 예비요청과 본 요청이다. 물론 다음에 설명할 Simple Request만 보내는 상황도 있지만 해당 개념을 설명할때 자세히 설명하겠다.</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/7fc27f54-9145-4af3-b4ea-80af06dcd22e/image.png" alt=""></p>
<p>예비 요청으로는 본요청의 METHOD가 아닌 OPTIONS 메서드를 이용한다.
이 요청은 본 요청을 보내기 전에 브라우저가 요청을 보내는 것이 안전한지 확인하는 과정이다.
과정을 살펴보자면</p>
<blockquote>
<ol>
<li>웹에서 API 요청</li>
<li>브라우저에서 예비요청을 보냄</li>
<li>서버에서는 응답으로 어떤 메서드를 허용할 것인지, 어떤 헤더를 허용할 것인지 등을 보내준다.</li>
<li>브라우저는 예비 요청의 응답을 확인하고 본 요청을 보낸다.</li>
<li>본 요청을 받고 응답 데이터를 웹으로 정상적으로 보낸다.</li>
<li>실패한다면 당근 console에 오류메시지를 뿜어줄 것이다.</li>
</ol>
</blockquote>
<br />

<h3 id="🤔-simple-request">🤔 Simple Request</h3>
<p>단순요청은 위에서 설명한 예비요청을 보내지 않고 <strong>바로 본요청을 서버로 보내는 경우</strong>이다. 브라우저는 응답의 헤더를 보고 CORS 정책을 위반하는지 검사한다.
그럼 언제 예비 요청을 보내고 언제 단순 요청을 보낼까?
단순요청이 동작되는 경우는 매우 까다롭다. 아래의 경우를 모두 만족할 경우에만 단순요청을 보낸다.</p>
<blockquote>
<ul>
<li>GET, HEAD, POST의 요청이어야 하며</li>
</ul>
</blockquote>
<ul>
<li>Custom Header 즉, 브라우저가 추가하는 헤더가 아닌 직접적으로 추가한 헤더가 있으면 안된다.</li>
<li>Content-Type는 다음의 값들만 가능하다.<ul>
<li>application/x-www-form-urlencoded</li>
<li>multipart/form-data</li>
<li>text/plain</li>
</ul>
</li>
</ul>
<p>Content-Type은 대부분 <code>application/json</code>으로 통신하기 때문에 단순 요청이 일어나기는 쉽지 않다.</p>
<br />

<h3 id="🤔-credentialed-request">🤔 Credentialed Request</h3>
<p>예비 요청에서 보안을 강화할때 사용한다.
기본적으로 웹에서 요청하는 API들은 옵션을 지정해주지 않으면 브라우저 내의 쿠키 나 인증관련된 헤더를 담지 않는다.
이런 정보들을 담을 수 있게 해주는 것이 <code>credential</code>이다. 값으로는
<code>same-origin</code> 같은 출처 간 요청에만 인증 정보를 담을 수 있음
<code>include</code> 모든 요청에 인증 정보를 담음
<code>omit</code> 모든 요청에 인증 정보를 담지 않음.</p>
<p>이 옵션을 사용한다면 CORS 검사하는 룰이 추가된다</p>
<blockquote>
<ul>
<li>서버 응답헤더에 <code>Access-Control-Allow-Origin</code>의 값으로 와일드카드(*)를 사용할 수 없다. 명시적인 URL을 작성해야한다</li>
</ul>
</blockquote>
<ul>
<li>서버 응답헤더에 <code>Access-Control-Allow-Credentials</code>라는 헤더와 그 값아 true로 설정되어 있어야한다.</li>
</ul>
<br />
<hr>
<br />

<h2 id="more-and-more">More and More</h2>
<h3 id="🤔-이-복잡합-cors-처리는-왜하는-걸까">🤔 이 복잡합 CORS 처리는 왜하는 걸까</h3>
<p>CORS가 존재하지 않고 모든 곳에서 데이터 요청이 가능하다면 다른 사이트에서 원본을 흉내내고 세션을 탈취해 공격이 가능해지게 된다. 이런 공격을 브라우저가 보호하기 위함이며 필요한 경우에는 서버측과 협의하여 요청을 보내기 위해서 존재한다.</p>
<h3 id="🤔-왜-모바일이나-post-man에서는-에러가-안나는가">🤔 왜 모바일이나 Post man에서는 에러가 안나는가</h3>
<p>CORS는 말 그대로 브라우저의 정책이다. 모바일이나 Post man은 브라우저를 거치지 않기 때문에 브라우저가 하는 역할을 수행하지 않을 것이다.</p>
<h3 id="🤔-access-control-request-headers">🤔 Access-Control-Request-Headers</h3>
<p>브라우저가 요청시 추가하는 요청헤더이다. 어떤 커스텀 헤더들이 들어가는지 명시되어 있다.</p>
<h3 id="🤔-cors-해결-방법">🤔 CORS 해결 방법</h3>
<ul>
<li>로컬에서 개발시 크롬 익스텐션을 이용해 CORS 정책 사용을 끄는 방법이 있지만, 실제 배포하고 나서 문제가 된다. 애초부터 사용하지 않는 것을 권장한다. 사실상 해결방법이 아닌 지금 당장 동작시키기 위한 반창고 붙이기 밖에 되지 않는다.</li>
<li>중간에 proxy서버를 거쳐가는 방법이 있다. <code>브라우저 - proxy - 서버</code> 순으로 동작하게 하여 proxy서버측에서 서버에서 받은 응답헤더를 조작하여 브라우저에게 보내주는 방법이다. </li>
<li>가장 좋은 방법으로 서버의 응답헤더에 재대로 추가해주는 것이 좋다. 응답헤더의<code>Allow-Control-AlLow-Origin</code> 값에 가능한 교차출처들을 넣어주는것이 좋다.</li>
</ul>
<br />

<p><strong>참고 블로그</strong>
<a href="https://evan-moon.github.io/2020/05/21/about-cors/">even-moon님의 블로그</a>
<a href="https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F">인파_님의 블로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git 전략 - Git-Flow 브랜치 전략]]></title>
            <link>https://velog.io/@rokwon_k/Git-%EC%A0%84%EB%9E%B5-Git-Flow-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@rokwon_k/Git-%EC%A0%84%EB%9E%B5-Git-Flow-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Tue, 23 Aug 2022 02:55:09 GMT</pubDate>
            <description><![CDATA[<p>Git-Flow는 병합기반의 솔루션이며 여러 개발자가 하나의 저장소에서 협업할 때 매우 효율적인 전략이다.(기능개발 단계, 준비 및 테스트 단계, 배포 단계 등 각 단계를 효율적인 방법으로 나눈 브랜치 전략)</p>
<p>우리팀은 다수의 인원이 하나의 저장소에서 함께 개발하지는 않지만 Github Action(github workflow 전략)을 적용함에 있어 Git-Flow 전략대로 가져가는게 좋다 판단(단계별로 workflow 작성)하여 Git-Flow 도입을 결정하였다. 겸사겸사 탄탄한 협업 능력도 기르고~</p>
<p><a href="https://danielkummer.github.io/git-flow-cheatsheet/index.ko_KR.html">Git-Flow</a></p>
<h2 id="git-flow-core-기능--규칙">Git-Flow Core 기능 + 규칙</h2>
<p>Git-Flow 전략은 5개의 큰 줄기의 branch가 존재한다.</p>
<ul>
<li>master<ul>
<li>release에서 테스트 완료되어 제품으로 출시될 브랜치(이 브랜치로 merge시 배포 자동화)</li>
<li>현재 스토어에 배포된 버전임. 문제 발생 시 → hotfix 브랜치로 checkout</li>
</ul>
</li>
<li>release<ul>
<li>출시 준비중인 브랜치 (version tag가 달려있음)</li>
<li>여기서 앱 테스트</li>
<li>앱 테스트 중 오류 수정 &amp; 내부 QA 통과 시 master로 merge</li>
</ul>
</li>
<li>develop<ul>
<li>기능개발의 시작점이자 개발종료 후 merge 될 곳</li>
<li>기능개발 된 이후 release로 version 달아서 머지</li>
</ul>
</li>
<li>feature<ul>
<li>기능개발 브랜치</li>
</ul>
</li>
<li>hotfix<ul>
<li>출시된 버전(master branch)에서 발생한 버그를 수정하는 브랜치</li>
<li>수정 완료시 master 및 develper branch에 merge</li>
</ul>
</li>
</ul>
<br />

<h3 id="💻-git-flow-설치">💻 Git-Flow 설치</h3>
<ul>
<li>git flow 전략대로 branch 틀을 만들어주고 git flow 명령어 사용가능하게 해줌</li>
</ul>
<pre><code class="language-bash"># git flow 설치
brew install git-flow-avh
# 해당 repository root에서
git flow init</code></pre>
<br />

<h3 id="🙋🏻-새-기능-개발--개발완료">🙋🏻 새 기능 개발 &amp; 개발완료</h3>
<ul>
<li>새 기능의 개발은 <code>develop</code>에서 시작 즉, develop 브랜치에서 checkout 해야함.</li>
<li>새 기능 branch의 이름은 <code>feature/{Jira 티켓넘버}</code>(우리 팀에서는 branch 명을 <code>{branch}/{Jira 티켓번호}</code>로 설정하기로 했다.)</li>
<li>아래는 새 기능 branch 만들기 명령어 및 기능완료 명령어</li>
</ul>
<pre><code class="language-bash">git flow feature start {Jira 티켓넘버}
# develop에 merge &amp; 해당 branch 삭제 &amp; develop브랜치로 자동 전환
git flow feature finish {Jira 티켓넘버}
# 그러나 우린 GUI툴인 Fork를 사용한다!</code></pre>
<br />

<h3 id="😃-새-버전-출시-준비--테스트">😃 새 버전 출시 준비 &amp; 테스트</h3>
<ul>
<li><code>develop</code> 내에서 기능개발 완료 시 → <code>release/vX.X.X</code> 로 merge<ul>
<li>develop에 merge되면 release로 merge하는 자동화(Github Action 사용할 예정)</li>
</ul>
</li>
<li><code>release</code> 브랜치로 push 되면 테스트 버전 앱 생성(server는 stage라 부르기로 하자)<ul>
<li>iOS(테스트 플라이트로 업로드), Android(APK추출 및 Slack으로 뿜뿜)</li>
<li>Firebase App Distribution는 나중에 도입하기로 하자</li>
</ul>
</li>
<li>테스트 하면서 오류는 <code>release</code> 에 수정 및 push</li>
<li>테스트 완료 시 <code>master</code> 에 merge</li>
</ul>
<br />

<h3 id="🥳-새-버전-출시">🥳 새 버전 출시</h3>
<ul>
<li><code>master</code> 에 merge 시 배포 자동화 로직 수행</li>
<li>앱 배포!</li>
</ul>
<br />

<h3 id="😭-출시-버전에서-오류">😭 출시 버전에서 오류</h3>
<ul>
<li>출시 된 버전에서 예상치 못한 오류 발생시 <code>master</code> 에서 <code>hotfix/{Jira 티켓번호}</code> 로 checkout</li>
<li>hotfix 수정완료시 <code>develop</code> 및 <code>master</code> 에 merge시키기 (자동화?)</li>
</ul>
<br />

<h3 id="🧑🏻💻-전체적인-흐름">🧑🏻‍💻 전체적인 흐름</h3>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/ccd260cc-5a6e-453a-a6e0-31ac09c6ea6b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Action을 이용한 Github -> Gitlab Mirroring]]></title>
            <link>https://velog.io/@rokwon_k/Github-Action%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Github-Gitlab-Mirroring</link>
            <guid>https://velog.io/@rokwon_k/Github-Action%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Github-Gitlab-Mirroring</guid>
            <pubDate>Sun, 21 Aug 2022 17:22:53 GMT</pubDate>
            <description><![CDATA[<p>소프트웨어 마에스트로 과정을 진행하면서 코드 관리를 Gitlab으로 하라고 했다. 우리팀은 Github로 관리를 하고 있었지만 평가시에도 반영이 되니 무시할 수는 없는 노릇이었다. 그렇다고 Github에도 올리고 Gitlab에도 올리는 방식은 할 일이 두배로 느는 방식이었기에 당연하게도 자동화를 하기로 하였고 우리가 현재 사용하고 있는 CI툴인 Github Actions을 이용하기로 결정했다.</p>
<p>전체적인 플로우는 아래와 같으며 로직은 정말 간단하다.
<img src="https://velog.velcdn.com/images/rokwon_k/post/9cf6434f-faaf-44d9-a735-20320db78517/image.png" alt=""></p>
<blockquote>
<ul>
<li>push 이벤트 발생 -&gt; Github Action Trigger </li>
</ul>
</blockquote>
<ul>
<li>job에서 해당 깃 레포를 checkout </li>
<li>Gitlab SSH를 이용해서 원격으로 지정한 레포로 그대로 push 해준다.</li>
</ul>
<p><br/><hr></p>
<h2 id="gitlab-ssh-연결">Gitlab SSH 연결</h2>
<p>먼저 Gitlab에서 내 계정을 SSH로 원격 수정할 수 있도록 key pair를 만들어 줘야한다.
터미널(또는 git bash)을 열고 아래명령어를 이용해 ssh key를 만든다. (처음에 keypair를 만들 경로를 설정하라고 하는데 그냥 Enter치면 기본 경로에 설정된다 ~/.ssh/이름, 나머지는 전부 Enter)</p>
<pre><code>ssh-keygen -t rsa -b 2048 -C &quot;별명 원하는 걸로&quot;</code></pre><p>만들어진 key는 설정된 경로에 <code>키이름.pub</code> 파일과 <code>키이름</code> 파일 이렇게 두 개가 생성된다. <code>키이름.pub</code>파일은 공개키이고 <code>키이름</code> 개인키로 각각 Gitlab, Github에서 사용될 예정이다.</p>
<br/>

<h3 id="gitlab-ssh-공개키-등록">Gitlab SSH 공개키 등록</h3>
<p>먼저 방금 생성한 <code>키이름.pub</code>파일을 열어 내용을 모두 복사해준다.
해당 내용을 Gitlab 내 계정에 SSH Key로 등록할 예정이다.
Gitlab 내 계정정보로 들어가 <code>SSH Keys</code> 메뉴를 선택해준다</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/6245384a-1798-41cb-ac0c-9df9765bfdce/image.png" alt=""></p>
<p>위의 Key 부분에 복사한 내용을 붙여넣고 Title 작성후 Add Key 버튼을 누르면 완료!</p>
<p><br/><br/></p>
<h3 id="gitlab-repository-권한-설정">Gitlab Repository 권한 설정</h3>
<p>이후에 Github Actions의 Runner에서 실행이 될때 아래와 같은 에러가 날 수 있다.</p>
<pre><code>[remote rejected] master -&gt; master (pre-receive hook declined)</code></pre><p>이는 Gitlab Repository의 Protected branches가 설정되어 있기 때문에 그런데 이를 해제해 줘야한다. 
<img src="https://velog.velcdn.com/images/rokwon_k/post/43c061db-9597-4aca-baef-efb56f4d7414/image.png" alt=""></p>
<p>위와 같이 gitlab Repository의 Settings - Repository에 Protected branches에서 아래의 Unprotect 버튼을 눌러 보호조치를 풀어버리자! push권한을 설정해 준다는 것인데 필요없으므로 Unprotect!</p>
<p><br /><br/></p>
<h3 id="github-ssh-개인키-등록">Github SSH 개인키 등록</h3>
<p>이제 Github에 개인키를 등록해 workflow내에서 이 key를 이용해 gitlab에 원격으로 접속할 수 있도록 해보자
Github의 해당 Repo로 들어가 Settings를 클릭한 후 좌측메뉴바에서 Security - Secrets - Actions를 클릭한다.
<img src="https://velog.velcdn.com/images/rokwon_k/post/5db005b8-58d1-4682-984b-68e2a80c17e6/image.png" alt=""></p>
<p>클릭했으면 상단에 New Repository token 버튼을 클릭한다.
내부에서 Name(원하는대로)과 Value(방금 만든 <code>키이름</code> 파일의 내용을 복붙)을 작성 후 완료!</p>
<blockquote>
<p>💡여기서 조심할 부분은 <code>키이름</code>의 내용을 전부 복사해야한다
-----BEGIN OPENSSH PRIVATE KEY----- 이렇게 적혀있는 부분도 놓쳐선 안된다!</p>
</blockquote>
<p>작성을 완료하면 아래와 같이 등록된 것을 확인할 수 있다. 필자는 <code>GITLAB_SSH_KEY</code>라는 Name으로 저장하였다.
<img src="https://velog.velcdn.com/images/rokwon_k/post/5582bc97-1c36-438a-a462-b03fccf9f494/image.png" alt=""></p>
<p><br/><hr></p>
<h2 id="github-action-workflow-작성">Github Action Workflow 작성</h2>
<p>마지막으로 workflow를 작성해보자 이 Workflow에서 2가지의 action을 사용한다.</p>
<blockquote>
<p>💡 action이란?
&#39;명령어들의 집합으로 이루어진 작업&#39;으로 내가 직접 만들 수도 혹은 Github에서 제공하는 Marketplace에서 다른 사람이 만들어놓은 것을 가져와서 사용할 수 있다.(라이브러리처럼)</p>
</blockquote>
<br />

<h3 id="checkout-action">checkout action</h3>
<p>우리의 job은 클라우드 가상환경의 runner에서 실행된다.
그곳에는 아직 우리의 repository가 없기에 git init, fetch하고 checkout하고 등 여러 작업들을 거쳐야한다. 그러나, actions에서 제공하는 &#39;actions/checkout@v3&#39;라는 action이 있으므로 이를 가져와서 쓴다!</p>
<br />

<h3 id="mirroring-action">mirroring action</h3>
<p>이제 runner상에서 코드를 받았으니 gitlab에 mirroring 하는 action을 취하면 된다. 그러나 이것또한 누군가 잘 만들어주신 action이 있기에 가져다가 쓰면된다!😁</p>
<p>아래의 action으로 gitlab뿐만 Bitbucket 등 여러 깃 저장소로의 미러링을 지원한다.
<a href="https://github.com/pixta-dev/repository-mirroring-action">pixta-dev/repository-mirroring-action@v1</a></p>
<br />

<h3 id="workflow">workflow</h3>
<pre><code class="language-yaml">name: sync to gitlab

on: [push, delete]

jobs:
  to_gitlab:
    runs-on: ubuntu-latest
    steps: 
      - uses: actions/checkout@v2 # checkout
        with:
          fetch-depth: 0
      - uses: pixta-dev/repository-mirroring-action@v1 # mirroring
        with:
          target_repo_url:
              # gitlab repo url ex) git@git.~~~~
          ssh_private_key:
            ${{ secrets.GITLAB_SSH_KEY }} # github에 등록한 key name</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github Action과 필요한 개념정리]]></title>
            <link>https://velog.io/@rokwon_k/Github-Action%EA%B3%BC-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@rokwon_k/Github-Action%EA%B3%BC-%ED%95%84%EC%9A%94%ED%95%9C-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 21 Aug 2022 09:18:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/rokwon_k/post/9fed2d77-d8ab-47df-ad21-753c57c5366b/image.png" alt=""></p>
<p>대부분의 프로젝트에서는 CI/CD를 사용하고있다. CI/CD는 대체 뭐고 왜 이를 제공해주는 툴들을 많이들 사용하는 걸까?(Jenkins라던지 Github Action이라든지)</p>
<p>궁금증을 해결하기 위해서 CI/CD란 무엇을 뜻하는 것이고 CI툴 중에서 Github Action에 대해서 알아보자.</p>
<h2 id="cicd란-무엇이고-왜-필요할까">CI/CD란 무엇이고 왜 필요할까?</h2>
<p>CI/CD의 목적은 무엇인가?</p>
<p><strong>반복적인 일</strong>(빌드, 테스트, 배포 작업 등)들을 처리하고 문제가 있을 때 경고를 해주는 등 <strong>자동화된 파이프라인</strong>을 통해서 코드 변경과 배포 단계를 원활하게 진행할 수 있다.
즉, 자동화를 통해 시간 절약 및 사람이 직접적으로 처리할 때 실수할 수 있는 부분들을 관리할 수 있다.</p>
<p><strong>CI</strong>(Continuous Integration)는 지속적인 통합을 뜻한다.</p>
<ul>
<li>추가,변경(push나 pull request)된 코드를 빌드하고 테스트 하는 프로세스를 자동화하는 것</li>
<li>위와 같은 프로세스를 거친후에 병합(통합) 즉, 코드를 merge해주는 것</li>
<li>다수의 인원이 함께 개발할때 충돌과 같은 일들을 미연에 방지 가능하며 Lint(코드 규칙)를 잘 지켰는지, 제대로 동작하는지 등 확인가능</li>
</ul>
<p><strong>CD</strong>(Continuous Delivery, Deployment)는 지속적인 제공 및 배포를 뜻한다.</p>
<ul>
<li>Delivery과정은 CI 과정을 거친후에 병합하여 실행테스트를 할 수 있도록 하는 것</li>
<li>Deployment는 뜻 그대로 배포 과정을 자동으로 처리 해주는 것이다.</li>
</ul>
<hr>
<h2 id="github-action이란">Github Action이란</h2>
<p><strong>Github Action이란?</strong>
Github 저장소를 기반으로 Workflow를 자동화 할 수 있는 도구. Github가 제공하는 완전관리형 CI/CD 툴이다.</p>
<h3 id="github-action-특징">Github Action 특징</h3>
<p><strong>특징</strong></p>
<blockquote>
<ul>
<li>컨테이너(도커) 기반으로 동작함</li>
</ul>
</blockquote>
<ul>
<li>개발자는 Workflow를 작성하여 다양한 이벤트를 기반으로 실행시킬 수 있다.</li>
<li>Workflow는 Runners(기본 Azure, self-hosted 가능)라 불리는 인스턴스에서 Linux, macOS, Windows 환경에서 실행됨 - 원하는 OS 지정할수 있음. 한 번에 여러 OS에서 테스트 가능</li>
<li>Gihub 마켓 플레이스에서 여러 사람이 공유한 Workflow를 찾아서 사용할 수도 있고 직접 만들어서 공유할 수도 있음</li>
<li>YAML로 작성<blockquote>
</blockquote>
</li>
</ul>
<p><strong>장점</strong></p>
<blockquote>
<ul>
<li>다른 CI/CD툴(Jenkins)들 처럼 서버설치가 필요하지 않음. 제공해주는 클라우드(Azure)가 있음</li>
</ul>
</blockquote>
<ul>
<li>비동기적 병렬실행이 가능한 CI/CD</li>
<li>Github 마켓 플레이스를 이용하여 Workflow를 가져다 쓰거나 공유할 수 있음.</li>
<li>Github에서 제공하는 완전 관리형 서비스이므로 설정이 매우 쉽움.<blockquote>
</blockquote>
</li>
</ul>
<p><strong>단점</strong></p>
<blockquote>
<ul>
<li>캐싱이 필요한 경우에는 자체 캐싱 로직을 작성해야함</li>
</ul>
</blockquote>
<ul>
<li>서버에 장애가 일어나거나 리소스를 초과할 경우 개발자가 직접 문제를 해결해야한다.<blockquote>
</blockquote>
</li>
</ul>
<h3 id="github-action-핵심-개념">Github Action 핵심 개념</h3>
<p><strong>Workflows</strong></p>
<blockquote>
<p><strong>자동화된 프로세스가 정의되어 있는 하나의 파일</strong>이다.
Github Action에서 <strong>가장 최상위 개념</strong>으로</p>
</blockquote>
<ul>
<li>YAML 파일로 작성됨</li>
<li>해당 파일을 트리거(실행)할 규칙, 실행할 동작이 작성되어 있음.<blockquote>
</blockquote>
</li>
</ul>
<p><strong>Runners</strong></p>
<blockquote>
<p><strong>Workflow가 실행될 인스턴스</strong>로 클라우드에서 동작한다.
다른 CI툴처럼 서버가 필요없는 이유로 기본적으로 Github Action이 인스턴스를 제공하기 때문이다.
기본적으로 MicroSoft의 Azure에서 동작하며 실행할 인스턴스(self-hosted-runner)를 등록할 수 있다.</p>
</blockquote>
<p><strong>Events</strong></p>
<blockquote>
<p><strong>Workflow를 실행할 특정한 활동(push 등)이나 규칙</strong>이다.
당연하게도 Workflow 파일 내에 정의해 놓는다. 
예를 들어, ‘git에 push 했을때’ 혹은 더 구체적으로 ‘어떤 branch에 push나 pull request를 했을때’ 등의 규칙을 정의할 수 있다.</p>
</blockquote>
<p><strong>Jobs</strong></p>
<blockquote>
<p>Workflow내에서 실행될 명령.
Event로 Workflow가 실행되면 Job에 작성된 명령들이 실행된다.</p>
</blockquote>
<ul>
<li>Workflow내에서 여러개의 Job을 작성할 수 있다.</li>
<li>위에서 설명한 Runner에서 실행된다.</li>
<li>기본적으로 Job들은 병렬로 실행되지만 서로 의존관계를 가질 수도 있고 직렬로 실행할 수 있다.</li>
<li>Job은 자신의 환경설정과 Steps를 가지고 있다.<blockquote>
</blockquote>
</li>
</ul>
<p><strong>Steps</strong></p>
<blockquote>
<p>Job내에 steps명령어 안에 존재한며 여러개의 step들로 구성되어 있다.</p>
</blockquote>
<ul>
<li>각 step들은 script, 명령어 또는 action을 실행할 수 있다.</li>
<li>각 step들은 데이터를 공유할 수 있다<blockquote>
</blockquote>
</li>
</ul>
<p><strong>Actions</strong></p>
<blockquote>
<p>재사용되는 명령어들의 집합으로 이루어진 작업이다. 컴포넌트라고 볼 수 있다.</p>
</blockquote>
<ul>
<li>직접 작성해도 되고 Markey에 등록되어 있는 Action을 가져와 사용할 수도 있다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h2 id="github-action-명령어-사용법">Github Action 명령어 사용법</h2>
<p>각 레포지토리의 최상단에 .github/workflows  디렉터리를 만들고 그 내부에 이름.yml 파일을 만들면 workflow 파일 생성 완료. 각 workflow 파일 내부에는</p>
<p><strong>최상단에 name 작성 :</strong> workflow의 이름을 설정해주는 것</p>
<pre><code class="language-yaml">name: workflow test</code></pre>
<p><strong>workflow를 실행시킬 Event정의</strong> : 아래는 master branch에 push이벤트가 일어날 때 라는 뜻</p>
<pre><code class="language-yaml">on: 
    push:
        branches:
            - master</code></pre>
<p><strong>Event에 부합했을때 실행 시킨 job정의</strong> </p>
<ul>
<li>바로 밑에 해당 job의 custom 이름 정의</li>
<li>설명 및 어떤 환경에서 돌릴것인지 정의 ubuntu, widnow, mac 등 + 버전까지</li>
</ul>
<pre><code class="language-yaml">jobs: 
    workflow-test-job: 
        runs-on: ubuntu-latest</code></pre>
<p><strong>Step 정의</strong></p>
<ul>
<li>각 스텝마다 name정의 및 uses(마켓에 있는 action 가져오기)<ul>
<li>market에서 가져오는 경우 <code>{owner}/{repo}@{ref|version}</code> 의 형태</li>
</ul>
</li>
<li>run은 직접 명령어 실행</li>
</ul>
<pre><code class="language-yaml">jobs:
    workflow-test-job:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout repo
                uses: action/checkout@v3

            - name: Use Node.js
                uses: actions/setup-node@v3
        with:
          node-version: &#39;14&#39;

            - name: Install npm
                run: npm install</code></pre>
<hr>
<h2 id="github-action-사용시-자주-실수하는-것">Github Action 사용시 자주 실수하는 것</h2>
<ul>
<li>github에서 사용하는 PAT(Personal Access Token)<ul>
<li>workflow(Update GitHub Action workflows) 체크하기</li>
</ul>
</li>
<li>외부 private repository에 접근할때<ul>
<li>해당 repo의 secret에 PAT를 넣고 접근을 위해 환경설정 필요</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter? RN? 모바일 인프라 구축기 with bloc & clean architecture]]></title>
            <link>https://velog.io/@rokwon_k/Flutter-RN-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B5%AC%EC%B6%95%EA%B8%B0-with-bloc-clean-architecture</link>
            <guid>https://velog.io/@rokwon_k/Flutter-RN-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%9D%B8%ED%94%84%EB%9D%BC-%EA%B5%AC%EC%B6%95%EA%B8%B0-with-bloc-clean-architecture</guid>
            <pubDate>Wed, 17 Aug 2022 17:25:14 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@rokwon_k/%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%8B%9C%EC%9E%91-%EC%A0%84-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0"><strong>서비스 개발 전 검증하기</strong></a> 이후 Flutter로 어플을 만들기로 결정하였다.
곧바로 Flutter로 만들기로 결정한 것은 아니었고 Android와 iOS 모두를 빠르게 구축하는데 크로스플랫폼만큼 좋은 방안은 없었기에 React Native와 Flutter 중 선택을 하였다.</p>
<br/>

<h2 id="react-native-vs-flutter">React Native vs Flutter</h2>
<p>과거에 RN으로 개발과 배포를 한 적은 있지만 거의 3년 가까이 다시 써본적도 없었고 React 개발경험도 3년 전 이후에는 없었기에 RN에서 익숙함이라고는 찾아볼 수 없는 수준이었다. 고로 두 프레임워크 모두 숙련도가 없는 상태에서 비교하여 선택하였다.</p>
<h3 id="rn의-장단점">RN의 장단점</h3>
<blockquote>
<p><strong>장점</strong></p>
</blockquote>
<ul>
<li>js/ts를 사용하며 React와 거의 비슷한 수준의 문법</li>
<li>몇 년 동안이나 크로스플랫폼의 강자로 남아있으면서 남긴 오픈소스들</li>
<li>expo와 같이 native를 건들이지 않으며 배포를 쉽게 해주는 플랫폼이 존재</li>
<li>핫 리로딩 지원(개발 시 다시 빌드를 하지 않고 코드 수정 후 저장만하면 리로딩)</li>
<li>코드 푸시 지원(배포 후 앱을 업데이트 출시하지 않고도 원격 수정 가능)</li>
</ul>
<blockquote>
<p><strong>단점</strong></p>
</blockquote>
<ul>
<li>js코드가 네이티브 코드로 변환되어서 어플이 동작함(속도가 느려짐. But, 간단한 앱에서는 차이를 못 느끼긴 함)</li>
<li>하지만 네이티브 코드로 변환되는 문제점때문에 Crashlytics와 같이 충돌을 잡아주는 서비스에서 충돌 지점을 명확히 알 수 없는 문제점이 있음</li>
<li>아직도 메이저 버전이 없음(3년전에 0.60대 였는데 아직도 0.60대) -&gt; 그러지는 않겠지만 아예 지원 중단 될 가능성이 있음. 즉, 생태계의 죽음.</li>
</ul>
<br />

<h3 id="flutter의-장단점">Flutter의 장단점</h3>
<blockquote>
<p><strong>장점</strong></p>
</blockquote>
<ul>
<li>구글의 지원을 받음(최근 들어 Flutter 3.0까지 발표)</li>
<li>RN처럼 코드 변환을 하는 것이 아닌 자체적인 UI엔진을 가지고 있어 네이티브와 흡사한 속도를 가짐</li>
<li>개발시 핫 리로딩 지원(React Native와 마찬가지)</li>
<li><a href="https://github.com/facebook/react-native">RN</a>과 <a href="https://github.com/flutter/flutter">Flutter</a> 깃허브 스타 수로 비교했을때 압승</li>
<li>근래 대부분 Flutter를 찬양함(이게 중요한 이유는 생태계를 만들어 가는 지지자들이 많다는 것)</li>
</ul>
<blockquote>
<p><strong>단점</strong></p>
</blockquote>
<ul>
<li>dart라는 생소한 언어. 아직까지 Flutter 말고는 쓸모를 찾지 못했다(구글이 만들고 있다는 만능OS 퓨시아에 dart 코드가 들어있다는 것 밖에)</li>
<li>RN의 expo 같은 것들이 없기에 네이티브 코드를 만질일이 많다는 것.</li>
<li>너무 빠른 버전 업그레이드(라이브러리 등 사용법이 휙휙 바뀜)</li>
</ul>
<br />

<h3 id="🧐-그래서-선택은">🧐 그래서 선택은?</h3>
<p>우선적으로 나는 2년 동안이나 iOS 개발자로서 현업에 있었기에 네이티브 코드에 대한 거부감이 없었다.(크로스 플랫폼에서 네이티브 오류는 대부분 iOS 오류...) 오히려 땡큐였고 이와 관련된 문제점은 배제하였다. 결국 플러터로 결정하였는데 이유는 아래와 같다.</p>
<blockquote>
</blockquote>
<ul>
<li>Flutter가 이제는 더 메이저하다는 것(구글이 버리지 않았어!)</li>
<li>네이티브와 필적하는 속도(언젠가 네이티브로 마이그레이션 할 시기를 더 늦출 수 있다!)</li>
<li>dart라는 생소한 언어이지만 새로운 언어에 대한 거부감을 일체 가지고 있지 않다</li>
<li>유일하게 RN의 &#39;코드 푸시&#39; 때문에 고민하였지만 결론적으로 Flutter를 결정하였다.</li>
</ul>
<p><br /><hr><br /></p>
<h2 id="flutter-인프라-구축">Flutter 인프라 구축</h2>
<p>서비스를 위해서 곧바로 기능 개발에 들어가도 문제는 없지만 이후를 생각했다. 운이 좋게도 나는 모바일 생태계에서 2년간 구른 경험이 있었기에 서버가 개발되기 전에 모바일 인프라를 고민하고 구축할만한 충분한 시간을 가질 수 있었다.</p>
<br />

<h3 id="❓-그렇다고-굳이-인프라-구축을">❓ 그렇다고 굳이 인프라 구축을?</h3>
<p>바로 개발에 들어가도 물론 문제 없을 것이다. 하지만 내 능력으로 어느정도 빠르게 해결할 수 있는 부분이고 앞으로 우리 서비스를 운영해가며 개발 시간을 단축시키기에 필요한 문제라고 생각했다.</p>
<br />

<h3 id="🤜-클린-아키텍쳐-도입">🤜 클린 아키텍쳐 도입</h3>
<p>모바일 안에서는 많은 종류의 로직들을 처리해야한다.</p>
<blockquote>
<ul>
<li>UI구현</li>
</ul>
</blockquote>
<ul>
<li>데이터에 따른 UI 업데이트</li>
<li>네트워크 통신</li>
<li>local DB와의 통신,</li>
<li>여러 사이드 이펙트 처리 등</li>
</ul>
<p>이 모든 것을 뒤죽박죽 섞어 사용하게 되면 개발을 하면 할수록 나도 모르게 발생하는 오류들이 점차 많아 질것이다.</p>
<p>관심사를 분리하여(같은 성향을 가진 것들을 하나로 묶기) 각 Layer간의 관계를 느슨하게 연결함으로써 <strong>코드 변경에 빠르고 유연하게 대처할 수 있도록 로직을 짜는 것</strong>이 중요하다.</p>
<p>클린아키텍쳐에 대해서 설명은 잘 설명된 블로그가 있어 참조해왔다 - <a href="https://techblog.woowahan.com/2647/">https://techblog.woowahan.com/2647/</a></p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/dbf824c7-172d-4401-bda7-58bb7915d50b/image.png" alt="">
출처  <a href="http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html</a></p>
<br />

<h3 id="🤜-상태관리-bloc-도입">🤜 상태관리 bloc 도입</h3>
<p>여러 상태 관리 라이브러리가 있지만 그 중에서 우리는 bloc을 이용하기로 했다.
bloc이 여러 선택지 중 러닝커브가 가장 높다고 평가받기는 하지만 앱의 규모가 커지면 커질수록 빛을 발하는 라이브러리이다. 게다가 iOS 개발을 하면서 TCA라는 상태관리 패턴을 사용해본 경험이 있었는데 이와 매우 유사하게 동작하는 패턴이었기에 거리낌없이 선택하였다.</p>
<p>bloc을 사용하는 이유는 공식문서에서도 잘 나와있듯이 <strong>UI로직과 비즈니스 로직</strong>의 분리이다. 이는 *<em>유지/보수의 측면에서 가독성, 재사용성 효율이 좋다(+테스트 용이) *</em></p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/077f976d-a708-4fbd-9b11-de244033dd8c/image.png" alt="">
<em>출처 : <a href="https://bloclibrary.dev/#/coreconcepts">bloc 공식문서</a>
참고 : <a href="https://www.youtube.com/watch?v=gc8-tS-6pe4">상태관리 라이브러리 비교</a></em></p>
<p>위 그림과 같이 bloc은 UI에서 비즈니스 로직을 따로 관리하며 그 내부에서 API와의 소통을 이루게 만들어져있다.
여기서 bloc 내부를 조금 더 뜯어보면 아래와 같은 형태로 상호작용을한다.</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/6ce77ee7-6047-4287-87ac-4a9cdd0b8080/image.png" alt=""></p>
<p>bloc은 Class형태이며 State 객체와와 Event 객체를 가진다.
bloc Class 자체는 Event Listener이며 Event 발생시 처리를 담당하고 있다.
Event는 말그대로 행위이며 State 상태를 나타낸다.
일반적으로 아래와 같이 동작한다.</p>
<blockquote>
<ul>
<li>UI내에서 터치나 스크롤 등이 일어났을때 bloc에 Event를 발생</li>
</ul>
</blockquote>
<ul>
<li>bloc에서 Event에 따른 행동 처리(API 통신 등)</li>
<li>해당 행위로 인하여 State를 변경</li>
<li>UI에서는 해당 State 변경에 반응하여 UI 업데이트를 진행한다.</li>
</ul>
<p>즉, UI에서는 Event의 발생 및 UI 업데이트 로직만을 처리하며 Bloc내에서 API 통신 등과 같은 비즈니스 로직을 수행 후 필요에 따라 State 변경을 처리한다.</p>
<br />

<h3 id="🤝-최종적인-그림">🤝 최종적인 그림</h3>
<p>위에서 설명한 클린아키텍쳐와 bloc을 접목하여 우리 프로덕트의 뼈대가 완성되었다.
<img src="https://velog.velcdn.com/images/rokwon_k/post/587c6bac-cc93-4fa8-bd2b-142f5a000979/image.png" alt=""></p>
<p><br /><hr></p>
<h2 id="next">Next?</h2>
<p>전체적인 프레임이 완성되었으니 이제는 보일러 플레이트 코드, 외부 기능과의 연동(푸시 알림, 소셜 로그인, admob 등)의 기능 구현에 대해 다루어 볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서비스 개발 전 검증하기]]></title>
            <link>https://velog.io/@rokwon_k/%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%8B%9C%EC%9E%91-%EC%A0%84-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@rokwon_k/%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%8B%9C%EC%9E%91-%EC%A0%84-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 13 Aug 2022 09:01:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>💡 당신이 김밥장사를 시작하고자한다. 장사가 흥하기 위해서 당신은 무엇을 먼저할 것인가?</p>
</blockquote>
<p>위 질문으로부터 우리의 서비스는 시작되었다. 무엇을 먼저할 것인가? 맛있는 김밥을 만들 수 있는 환상의 레시피 작성하기? 유동 인구가 많은 핫 플레이스에 매장 차리기? 누가봐도 예쁘다고 생각할만한 인테리어 시공?</p>
<p>아쉽게도 우리의 답은 이중에 있지 않다. 아무리 맛있고 화려하고 입지가 좋은 곳에 있다고 해도 고객이 없으면 무의미하다. 모든 것은 고객을 끌어드리기 위한 방법일 뿐이지 고객자체가 될 수 없다. 그래서 <strong>우리는 김밥을 살 고객을 먼저 확보</strong>하기로 하였다.</p>
<br/>

<h3 id="🤨-설문조사로-사용할지-알아보면-되지-않는가">🤨 설문조사로 사용할지 알아보면 되지 않는가?</h3>
<p>일반적으로 많이 사용하는 것이 설문조사이다. 
하지만 우린 근본적으로 <strong>&#39;이런게 있으면 사용하실 의향이 있겠습니까?&#39;</strong>와 같은 설문조사를 믿지 않는다. 설문조사는 설문조사 참여자의 적극적인 투자를 유도할 수 없다. &#39;오 있으면 좋겠네&#39; 하면서 체크하지만 실제로 앱이 나와도 사용하지 않을 가능성이 크다</p>
<p>그럼에도 설문조사라는 방법을 사용하는 이유는 매우 편하기 때문이다. 그리고 그 결과를 믿기만 하면 몸도 마음도 편해진다. 우리는 오류를 최대한 줄일 수 있는 방법을 생각해보면서 실서비스와 가장 유사한 방법으로 베타서비스를 실시하였다.</p>
<blockquote>
<p>&#39;아이디어 불패의 법칙&#39;에서 이야기하는 프로토타이핑을 우리만의 방식으로 적용해보았다.</p>
<p>💡 <strong>프리토 타이핑이란?</strong>
제품의 가장 단순한 버전을 만들어 아이디어를 빠르고 저렴한 비용으로 테스트하는 것을 말한다.</p>
</blockquote>
<p><br/><hr><br/></p>
<h2 id="🤔-어떻게-고객을-먼저-확보하지">🤔 어떻게 고객을 먼저 확보하지?</h2>
<p>곧바로 소프트웨어를 개발하는 것에는 많은 리소스를 소비하게 된다. 매장을 계약하고 장사를 준비하는 시간과 같다고 볼 수 있다.
이 아이디어가 성공할지도 모르는데 그 많은 리소스를 먼저 소비해야하는가? 이 문제말고도 프로덕트를 먼저 만들면 내가 만든 프로덕트에 정이 생겨버려 안되는 걸 알면서도 붙잡고 있을 가능성이 클 것이라고 생각했다.</p>
<p>아마 당신이 소프트웨어 개발자라면 먼저 개발하고 싶은 욕망을 참을 수 없을 것이다. 우리또한 그러하였지만 서비스를 위해 증명을 먼저 하기로 하였다.</p>
<p>우리의 해결책은 <strong>카카오톡 채널 운영</strong>이었다. 즉, 이미 만들어진 소프트웨어 위에서 빠르게 테스트를 진행해보는 것이다.
<img src="https://velog.velcdn.com/images/rokwon_k/post/0c436119-428a-416e-bb02-93658faf5338/image.png" alt=""></p>
<p>우리들은 채널을 만들고 Google Ads를 이용해 광고를 띄웠다! 
아래는 실제 우리가 띄운 랜딩 페이지이다.
<img src="https://velog.velcdn.com/images/rokwon_k/post/9a37a975-98d8-4130-9b33-056e14202b28/image.png" alt=""></p>
<hr><br/>

<h2 id="🧐-고객이-많이-모이면-장땡">🧐 고객이 많이 모이면 장땡?</h2>
<p>고객이 많이 모이게 되면 곧바로 서비스를 시작해도 괜찮을까? 그럼 광고에 돈을 많이 태우기만 하면 단순히 수를 채우는건 문제가 없지 않는가? </p>
<p>그래서 우리들은 여기서 그 이상을 보기로 하였다. 김밥을 살 고객들을 모았는데 김밥의 맛을 본 고객들이 맛이 없다며 다시는 사먹지 않는다면 고객을 모은 의미가 없지 않겠는가?</p>
<p>1.
그렇게 우리는 충분한 기간동안 카카오톡 채널을 운영하며 고객들이 다시 우리들의 김밥 사먹는지를 확인해보기로 하였다. 즉, <strong>리텐션까지 체크하기로 결정하였다.</strong></p>
<blockquote>
<p>💡 <strong>리텐션이란?</strong>
처음 고객이 유입된 이후로 계속해서 고객이 서비스를 이용하는지 측정한 것</p>
</blockquote>
<br/>

<p>2.
우리는 몇가지 레퍼런스를 찾아보았고 이 아이디어로 서비스개발을 확정지을 포인트를 결정하였다.</p>
<blockquote>
<p><strong>레퍼런스 및 의사결정</strong>
👉 높은 리텐션으로 유명한 &#39;틱톡&#39;의 4주차 리텐션은 <strong>10%</strong> 이다.
👉 에듀테크 카테고리의 4주차 평균 리텐션은 <strong>1.9%</strong>이다.
👉 우리는 서비스(카카오톡)에 의존하기 때문에 더 높은 리텐션이 나올 것이다.
👉 적어도 4주차 <strong>리텐션 2.5%</strong>는 넘겨야만 독립적인 서비스로서 가치가 있을 것이다.</p>
</blockquote>
<br/>

<p>3.
유저들의 참여를 권장하면서 실서비스에서 제공할 코어 기능 중 가장 중요시 되는 몇 기능만을 넣어 채널을 운영하였다.</p>
<blockquote>
<p>👉 매일 아침 오늘의 질문을 카톡으로 전송
👉 매일 8시 리마인더 톡 전송
👉 학습자료 링크 제공
👉 자정에 그날 답변자들 중 베스트 답변 10개를 우리가 선정하여 전송</p>
</blockquote>
<br/>

<p>4.
실제 서비스와의 몇 가지 차이점을 두었고 유저들이 이 차이점을 원한다면 진짜 소프트웨어가 필요할 때라고 생각하였다.</p>
<blockquote>
<p>👉 주관적으로 선정한 베스트 답변 및 최대 10개만 제공
👉 답변하지 않은 이전 질문들을 확인할 수 없음.
👉 등등...</p>
</blockquote>
<br/>

<p>위와 같은 방식으로 18일의 시간이 지났을때 우리는 서비스 개발을 확정지었다.(28일 동안 지켜보기로 했지만)
18일동안 이용한 유저의 수는 400여명이며 이중** 1주차 유입유저 130여명 중 45%<strong>가 넘는 유저가 3주차의 시점에서도 계속해서 이용하고 있었다. 다른 사람들이 들으면 정말 거짓말처럼 느껴지겠지만 **데이터가 수치로서 증명하고 있으니 우린 믿지 않을 도리가 없었다.</strong></p>
<p>뿐만아니라 개발시작하면서 우리 서비스를 이용하는 유저들을 대상으로 조사(설문조사지만)를 시작하였다. 100여명이 응답을 받았으며 그 결과에는 우리가 추측한 소프트웨어의 필요성을 얻을 수 있었다.
설문조사를 완벽히 믿지는 않지만 해당 답변이 서술형이었다는 점과 비슷한 대답을 받은 비율(정확한 비율은 비밀!)이 높았다는 점에서 적어도 개발하는 원동력이 될 수 있었다.</p>
<p><br/><hr><br/></p>
<h2 id="🏃-개발-start">🏃 개발 Start</h2>
<p>결과적으로 각 주차 유입 유저들의 4주차 평균 리텐션은 30%를 넘어섰음을 확인했으며 개발 시작 4주만에 모든 개발을 끝냈다. 지금은 애플 앱 스토어에 배포되어있으며 
플레이 스토어에는 아직 검토중에 있다.</p>
<p>아키텍쳐와 보안적인 부분들 또한 신경써서 작업했으며 이미 아이디어를 증명함으로써 팀 내부적으로 확신이 가지고 있었기에 달리기에 열중하였고 MVP의 빠른 개발을 마칠 수 있었다.(정말 다시 생각해도 우리팀은 미친거 같다😍)</p>
<p>아직 기존 서비스 유저들을 포함해서 앱 홍보를 하고 있지는 않다.
내부적으로 조금 회의를 거친 후 제대로 된 서비스를 운영해나갈 생각이다.
이 다음 세션은 우리의 초기 인프라 구축 세션이 될 듯 하다.
그리고 서비스를 위해 필요한 Flutter 기능 구현을 정리할 예정이고 배포하는 방법에 대해서 다루면서 약 2달간의 우리들의 여정에 대해 마무리할 것이다.</p>
<p>창업을 준비하시는 분들, Flutter로 앱을 배포하시는 분들 모두에게 좋은 시리즈가 되었으면 좋겠고 많은 피드백을 받았으면 좋겠다.</p>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/73b9c1d3-6071-464d-9865-50b92c3af6ee/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스타트업 with Flutter 개발 스토리]]></title>
            <link>https://velog.io/@rokwon_k/Flutter%EC%95%B1-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80-%EA%B0%9C%EB%B0%9C%EA%B8%B0-A-to-Z</link>
            <guid>https://velog.io/@rokwon_k/Flutter%EC%95%B1-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80-%EA%B0%9C%EB%B0%9C%EA%B8%B0-A-to-Z</guid>
            <pubDate>Fri, 12 Aug 2022 08:42:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🗣 지금의 나 : 대학3학년 휴학을 때리고 iOS 개발자로서 2년 가까이 보내면서 항상 가슴 한 편에 창업에 도전하는 꿈을 가지고 있었다.
그렇게 2022년이 찾아오고 오랫동안 꿈에서만 상상하던.. <strong>스타트업</strong>에 도전하기로 결심했다!
곧바로 소프트웨어 마에스트로 13기에 지원하였고 현재의 팀원들과 공유 오피스에서 밤낮 쉬지 않고 달려나가고 있는 중이다</p>
</blockquote>
<h2 id="이-글을-쓰는-이유">이 글을 쓰는 이유?</h2>
<p>오전 10시부터 새벽 1시까지 평일 주말 가릴꺼 없이 MVP 기획, 검증, 개발을 끝내고 드디어 배포를 하게 되었고 이번 연휴 3일간 달콤한 휴식을 취하기로 했다.🥳
겸사겸사 휴식기간 동안 지금까지 우리의 서비스 개발기를 블로그로 남겨보고자 한다.</p>
<p>전체적인 서비스 시작과 앱 개발기를 다루며 마지막으로 회고를 마지막으로 끝내고자 한다. 기획부터 기능 개발, 배포까지 꽤 많은 부분이 들어가기 때문에 시리즈로서 모듈별로 나누어서 제작하고자 한다. 이 글을 보고 우리와 같이 서비스를 시작하는 사람들 혹은 Flutter 앱을 개발하고자 하는 분들의 개발 시간을 단축시켜 줄 수 있었으면 좋겠다.(피드백 혹읜 색다른 의견은 언제나 환영입니다!)</p>
<h3 id="작성-계획">작성 계획</h3>
<p>총 4파트로 나누어 작성할 예정이며 당연하게도? 기능 구현 파트가 가장 길어질 거 같다.</p>
<ol>
<li><strong>서비스 시작</strong><ul>
<li>서비스 시작 전 검증하기</li>
<li>아키텍처 구축 - 상태 관리를 위한 bloc 패턴과 클린 아키텍처</li>
</ul>
</li>
<li><strong>Flutter 코어 기능 구현</strong><ul>
<li>Font 적용하기</li>
<li>API 통신으로 받아올 모델 정의 with Code_generator</li>
<li>API Generic Call (보일러 플레이트 코드)</li>
<li>API 내 Interceptor를 이용한 로그 및 인증로직 처리</li>
<li>local DB(sqflite) 적용기</li>
<li>토큰 및 사용자 데이터 영구 저장하기(메모리에 데이터 저장하기) with 암호화, 객체, 데이터 저장소</li>
<li>똑같은 이미지 매번 로드할꺼야? - 이미지 캐싱하여 사용하기</li>
<li>Firebase연동 crashlytics &amp; anlalytics 및 디버그 view를 이용해 연동 확인</li>
<li>앱 내 광고 띄우기 Admob 연동하기</li>
<li>앱의 첫 화면 - Splash화면 적용하기</li>
<li>kakao 로그인, apple 로그인 적용하기</li>
<li>푸시 알림 적용하기 및 딥링크 구현하기</li>
<li>시스템에서 알림을 off 했다면? - 앱 시스템 설정으로 보내기</li>
<li>iOS - ATT 적용하기</li>
</ul>
</li>
<li><strong>Flutter 트러블 슈팅</strong><ul>
<li>Firebase와 admob을 동시에 사용할때 의존된 라이브러리 버전 크래시 문제</li>
<li>iOS archieve 시 module not found 문제 -  podfile과 deploy target OS 맞추기</li>
<li>bloc 패턴 state가 변경해도 view가 바뀌지 않는 문제</li>
<li>sqflite 적용시 iOS 실행 문제</li>
</ul>
</li>
<li><strong>Flutter 앱배포하기</strong><ul>
<li>iOS app store connect</li>
<li>Android play store console</li>
</ul>
</li>
<li>마무리</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - Widget의 타입과 State]]></title>
            <link>https://velog.io/@rokwon_k/Flutter-Widget%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-State</link>
            <guid>https://velog.io/@rokwon_k/Flutter-Widget%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-State</guid>
            <pubDate>Mon, 11 Jul 2022 17:32:52 GMT</pubDate>
            <description><![CDATA[<p>Flutter에서는 위젯을 총 3가지로 나눌 수 있습니다.</p>
<ul>
<li>상태를 가지고 있지 않은 Stateless Widget,</li>
<li>상태를 가지고 있는 Stateful Widget.</li>
<li>전역으로 State를 가지고 있는 Inherited Widget</li>
</ul>
<p>각 위젯이 어떻게 다른지 살펴보도록 합시다!
먼저 Stateless와 Stateful Widget을 비교하며 살펴봅니다.</p>
<br/>

<h3 id="stateless-vs-stateful"><strong>Stateless vs Stateful</strong></h3>
<p>state의 경우 local state와  input data State(internal state)가 있습니다.
local state란 Class 내부에 존재하는 필드들이고 input data state란 Class 외부에서 주입한 인자들입니다.</p>
<p>Stateless의 경우 Redering되는 조건은 input data가 변경되었을 때 뿐이고
Stateful의 경우에는 input data든 local 변경되었을때 Rerendering됩니다.
물론 local state의 변화는 setState라는 메서드 내에서 이루어졌을때를 조건으로 합니다.</p>
<p>StatelessWidget는 상태변경이 필요없기 때문에 내부 필드(로컬 변수)들을 final로 선언합니다. </p>
<blockquote>
<p><strong>💡StatefulWidget이 State와 2개로 나뉘는 이유?</strong>
State를 관리하면서 State에 대한 생명주기를 가지고 있습니다. 이 State를 StatefulWidget이 가지고 있다면 Rebuild이 될때 생명주기 복구에 많은 비용이 들게 됩니다. State 클래스가 생명주기를 관리하면 State는 폐기되지 않으므로 데이터 변경에 언제든지 재구성할 수 있습니다. 즉, 성능 향상을 목적으로 이 둘을 분리했다고 할 수 있습니다.</p>
</blockquote>
<br/>

<h3 id="stateful-widget의-생명주기"><strong>Stateful Widget의 생명주기</strong></h3>
<p><img src="https://velog.velcdn.com/images/rokwon_k/post/c1087ff9-52f2-4822-9c58-e006c8ba18dd/image.png" alt=""></p>
<p>출처 : <a href="https://www.raywenderlich.com/books/flutter-apprentice/v1.0.ea3/chapters/4-understanding-widgets">https://www.raywenderlich.com/books/flutter-apprentice/v1.0.ea3/chapters/4-understanding-widgets</a></p>
<ol>
<li><strong>StatefulWidget에서 createState</strong>
상태생성</li>
<li><strong>mounted = true</strong>
화면에 위젯 mount</li>
<li><strong>initState()</strong>
상태를 초기화하며 위젯이 실행되고 한 번만 실행합니다</li>
<li><strong>didChangeDependencies()</strong>
해당 위젯이 의존하고 잇는 위젯이 변경이 변경되면 호출합니다</li>
<li><strong>build</strong>
화면에 위젯을 표시</li>
<li><strong>didUpdateWidget()</strong>
위젯이 업데이트되었을때 실행됩니다</li>
<li><strong>setState() 함수 실행</strong>
상태를 갱신, 위젯이 다시 build됩니다</li>
<li><strong>deactivate()</strong>
State가 위젯 트리에서 제거합니다</li>
<li><strong>dispose()</strong>
State가 위젯 트리에서 완전히 제거되며 더 이상 State 사용이 불가합니다</li>
<li><strong>mounted = false</strong>
위젯이 화면에서 사라집니다</li>
</ol>
<br/>

<h3 id="stateful-widget의-문제점"><strong>Stateful Widget의 문제점</strong></h3>
<p>어플이 복잡한 구조를 가질 때 상태를 표시하기 위해 불필요한 위젯들이 rebuild되면서 문제가 생길 수 있습니다. 그렇게 State에 대한 관리를 수월하게 하기 위해 상태 관리 라이브러리(Provider, Bloc 등)들이 탄생하게 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter - Widget의 build메서드와 BuildContext]]></title>
            <link>https://velog.io/@rokwon_k/Flutter-Widget%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-BuildContext</link>
            <guid>https://velog.io/@rokwon_k/Flutter-Widget%EC%9D%98-%ED%83%80%EC%9E%85%EA%B3%BC-BuildContext</guid>
            <pubDate>Mon, 11 Jul 2022 07:29:55 GMT</pubDate>
            <description><![CDATA[<p>StatelessWidget이나 StatefulWidget의 State<T>든 생성을 할 때 Widget을 리턴하는 build 메서드를 가진며 BuildContext 타입의 인자를 하나 받습니다. 이들의 역할을 무엇일까요?</p>
<br/>


<h2 id="build-메서드와-buildcontext-타입">build 메서드와 BuildContext 타입</h2>
<p>override메서드인 build 메서드는 <strong>구현한 UI 위젯들을 화면에 출력</strong>될 수 있도록 리턴해줍니다.</p>
<p>그렇다면 이 메서드에서 인수로 받는 BuildContext타입의 context는 무엇일지?
<strong>BuildContext 타입은</strong> <strong>현재 위젯의 위젯트리상에서 위치에 관한 정보를 담고 있습니다.</strong>
context는 어떤 위젯의 정보를 담고있을까? 바로 return 하는 위젯의 부모 위젯에 대한 위치 정보를 가지고 있습니다.</p>
<pre><code class="language-dart">class MyHomePage extends StatelessWidget {
  const MyHomePage({ Key? key }) : super(key: key);

  @override
    // 여기서 context는 이 MyHomePage를 부르는 부모위젯의 위치정보를 담고 있음
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( title: Text(&quot;MyHomePage&quot;), ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            const Text(&#39;Test&#39;, ),
          ],
        ),
      ),
    );
  }
}</code></pre>
<p><br/><br/></p>
<h2 id="context를-다루면서-자주하는-실수">Context를 다루면서 자주하는 실수</h2>
<h3 id="of-함수를-다룰-때"><strong>of 함수를 다룰 때</strong></h3>
<p>현재 위젯과 가장 가까운 어떠한 위젯을 찾을때 보통 of 함수를 많이 사용합니다.</p>
<pre><code class="language-dart">class MyHomePage extends StatelessWidget {
  const MyHomePage({ Key? key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( title: Text(&quot;MyHomePage&quot;), ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            ElevatedButton(
              onPressed: (){
                Scaffold.of(context).showSnackBar(SnackBar(content: Text(&#39;snack bar&#39;)));
              },
              child: Text(&#39;button&#39;),
            ),
          ],
        ),
      ),
    );
  }
}</code></pre>
<p>이때, 위 코드에서 Scaffold.of 함수는 인자로 제공해준 context에서 조상 중 가장 가까운 Scaffold를 반환해주는 역할을 합니다. 하지만 위에서 말했듯 저 context 부모의 위치가 담긴 BuilContext입니다. 즉, 현재 Scaffold보다 부모에 존재하는 Scaffold를 찾게되는 것입니다.</p>
<p>이를 해결하기 위해선 두가지의 방법이 존재합니다. <strong>Builder를 이용하는 방법</strong>과 <strong>새로운 widget class를 만들어 주는 방법.</strong></p>
<p><strong>Builder 이용하여 Scaffold 아래에 새로운 context를 생성시키기</strong></p>
<pre><code class="language-dart">class MyHomePage extends StatelessWidget {
  const MyHomePage({ Key? key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( title: Text(&quot;MyHomePage&quot;), ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            Builder(builder: (ctx) {
              return ElevatedButton(
                onPressed: (){
                  Scaffold.of(ctx).showSnackBar(SnackBar(content: Text(&#39;snack bar&#39;)));
                },
                child: Text(&#39;button&#39;),
              );
            })
          ],
        ),
      ),
    );
  }
}</code></pre>
<p><br/><br/></p>
<p><strong>새로운 widget class으로 만들어주기</strong></p>
<pre><code class="language-dart">class MyHomePage extends StatelessWidget {
  const MyHomePage({ Key? key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( title: Text(&quot;MyHomePage&quot;), ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: &lt;Widget&gt;[
            MyButton(),
          ],
        ),
      ),
    );
  }
}

class MyButton extends StatelessWidget {
  const MyButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: (){
        Scaffold.of(context).showSnackBar(SnackBar(content: Text(&#39;snack bar&#39;)));
      },
      child: Text(&#39;button&#39;),
    );
  }
}</code></pre>
<p>Builder 클래스는 내부 위젯들을 새로운 위젯으로 강제적으로 만들며 그 부모(여기선 Scaffold)의 context로 접근가능하게 만들어줍니다. 하지만 새로운 Widget class를 구현하는 것은 재사용성 측면에서 매우 뛰어날뿐만 아니라 가독성과 깔끔함까지 같이 가져갈 수 있습니다.</p>
<p>감사합니다😁</p>
]]></description>
        </item>
    </channel>
</rss>