<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>nowod_it.log</title>
        <link>https://velog.io/</link>
        <description>개발을 좋아하는 마음과 다양한 경험을 토대로 좋은 개발자가 되고자 노력합니다.</description>
        <lastBuildDate>Tue, 21 May 2024 05:33:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>nowod_it.log</title>
            <url>https://velog.velcdn.com/images/nowod_it/profile/1cd6335d-3646-4444-bdb8-d1942664a44c/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. nowod_it.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/nowod_it" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Prometheus, Grafana 기반 모니터링 환경 구축]]></title>
            <link>https://velog.io/@nowod_it/Prometheus-Grafana-%EA%B8%B0%EB%B0%98-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@nowod_it/Prometheus-Grafana-%EA%B8%B0%EB%B0%98-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Tue, 21 May 2024 05:33:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/nowod_it/post/901dcee9-3faa-4dc8-9018-516328245d72/image.png" alt=""></p>
<blockquote>
<p>Prometheus, Grafana, Node Exporter 기반의 모니터링 환경을 구축합니다.</p>
<p><strong>Prometheus</strong> - metrics 기반 시스템 로그 수집</p>
<p><strong>Node</strong> <strong>Exporter</strong> - 서버, Docker 등의 모니터링 정보를 Prometheus로 전달</p>
<p><strong>Grafana</strong> - 모니터링 시각화 (id: admin , pwd: vsDpcAdmin1@)</p>
</blockquote>
<h2 id="docker-기반-환경-세팅">Docker 기반 환경 세팅</h2>
<ol>
<li>먼저 설치 디렉토리를 생성한다.</li>
</ol>
<pre><code>mkdir prometheus-grafana
cd prometheus-grafana</code></pre><ol>
<li>Docker를 실행하기 위해 docker-compose.yml 파일을 생성한다. <strong>prometheus-grafana/docker-compose.yml</strong></li>
</ol>
<pre><code>version: &#39;3.7&#39;  # 파일 규격 버전
services:       # 이 항목 밑에 실행하려는 컨테이너 들을 정의
  prometheus:
    image: prom/prometheus
    user: root
    container_name: prometheus
    volumes:
      - ./prometheus/config:/etc/prometheus
      - ./prometheus/volume:/prometheus
    ports:
      - 9090:9090 # 접근 포트 설정 (컨테이너 외부:컨테이너 내부)
    command: # web.enalbe-lifecycle은 api 재시작없이 설정파일들을 reload 할 수 있게 해줌
      - &#39;--web.enable-lifecycle&#39;
      - &#39;--config.file=/etc/prometheus/prometheus.yml&#39;
    restart: always
    networks:
      - promnet

  grafana:
    image: grafana/grafana
    container_name: grafana
    # user: &quot;$GRA_UID:$GRA_GID&quot;
    ports:
      - 3000:3000 # 접근 포트 설정 (컨테이너 외부:컨테이너 내부)
    volumes:
      - ./grafana/volume:/var/lib/grafana
    restart: always
    networks:
      - promnet
    user: root

networks:
  promnet:
    driver: bridge</code></pre><ol>
<li>Prometheus 관련 설정 파일이 위치할 디렉토리를 생성한다.</li>
</ol>
<pre><code>mkdir prometheus
mkdir prometheus/config</code></pre><ol>
<li>/prometheus/config 디렉토리 안에는 설정 파일(prometheus.yml, rule.yml)을 생성한다.</li>
</ol>
<p><strong>prometheus-grafana/prometheus/config/prometheus.yml</strong></p>
<pre><code>global:
  scrape_interval: 15s     # scrap target의 기본 interval을 15초로 변경 / default = 1m
  scrape_timeout: 15s      # scrap request 가 timeout 나는 길이 / default = 10s
  evaluation_interval: 2m  # rule 을 얼마나 빈번하게 검증하는지 / default = 1m

  # Attach these labels to any time series or alerts when communicating with
  # external systems (federation, remote storage, Alertmanager).
  external_labels:
    monitor: &#39;codelab-monitor&#39;       # 기본적으로 붙여줄 라벨
  query_log_file: query_log_file.log # prometheus의 쿼리 로그들을 기록, 없으면 기록안함

# 규칙을 로딩하고 &#39;evaluation_interval&#39; 설정에 따라 정기적으로 평가한다.
rule_files:
  - &quot;rule.yml&quot;  # 파일 위치는 prometheus.yml 이 있는 곳과 동일 위치
  - &quot;rule2.yml&quot; # 여러개 가능

# 매트릭을 수집할 엔드포인드로 여기선 Prometheus 서버 자신을 가리킨다.
scrape_configs:
  # 이 설정에서 수집한 타임시리즈에 `job=&lt;job_name&gt;`으로 잡의 이름을 설정한다.
  # metrics_path의 기본 경로는 &#39;/metrics&#39;이고 scheme의 기본값은 `http`다
  - job_name: &#39;monitoring-item&#39; # job_name 은 모든 scrap 내에서 고유해야함
    scrape_interval: 10s      # global에서 default 값을 정의해주었기 떄문에 안써도됨
    scrape_timeout: 10s       # global에서 default 값을 정의해주었기 떄문에 안써도됨
    metrics_path: &#39;/asdf&#39;     # 옵션 - prometheus가 metrics를 얻기위해 참조하는 URI를 변경할 수 있음 | default = /metrics
    honor_labels: false       # 옵션 - 라벨 충동이 있을경우 라벨을 변경할지설정(false일 경우 라벨 안바뀜) | default = false
    honor_timestamps: false   # 옵션 - honor_labels이 참일 경우, metrics timestamp가 노출됨(true일 경우) | default = false
    scheme: &#39;http&#39;            # 옵션 - request를 보낼 scheme 설정 | default = http
    params:                   # 옵션 - request요청 보낼 떄의 param
      user-id: [&#39;myemail@email.com&#39;]

    # 그 외에도 authorization 설정
    # service discovery 설정(sd)

    # 실제 scrap 하는 타겟에 관한 설정
    static_configs:
      - targets: [&#39;localhost:9090&#39;, &#39;localhost:9100&#39;, &#39;localhost:80&#39;] ## prometheus, node-exporter, cadvisor
        labels: # 옵션 - scrap 해서 가져올 metrics 들 전부에게 붙여줄 라벨
          service : &#39;monitor-1&#39;

    # relabel_config - 스크랩되기 전의 label들을 수정
    # metric_relabel_configs - 가져오는 대상들의 레이블들을 동적으로 다시작성하는 설정(drop, replace, labeldrop)
</code></pre><p><strong>prometheus-grafana/prometheus/config/rule.yml</strong></p>
<pre><code>groups:
- name: example # 파일 내에서 unique 해야함
  rules:

  # Alert for any instance that is unreachable for &gt;5 minutes.
  - alert: InstanceDown
    expr: up == 0
    for: 5m
    labels:
      severity: page
    annotations:
      summary: &quot;Instance {{ $labels.instance }} down&quot;
      description: &quot;{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes.&quot;

  # Alert for any instance that has a median request latency &gt;1s.
  - alert: APIHighRequestLatency
    expr: api_http_request_latencies_second{quantile=&quot;0.5&quot;} &gt; 1
    for: 10m
    annotations:
      summary: &quot;High request latency on {{ $labels.instance }}&quot;
      description: &quot;{{ $labels.instance }} has a median request latency above 1s (current value: {{ $value }}s)&quot;
</code></pre><ol>
<li>/prometheus 디렉토리의 권한을 docker에서 수정할 수 있도록 변경한다.</li>
</ol>
<pre><code>sudo chmod -R 777 ./prometheus</code></pre><ol>
<li>최종 폴더 형태</li>
</ol>
<pre><code>.
├── docker-compose.yml
├── grafana
│   └── volume
│       ├── grafana.db
│       ├── grafana.db-journal
│       └── plugins
└── prometheus
    ├── config
    │   ├── prometheus.yml
    │   ├── query_log_file.log
    │   └── rule.yml
    └── volume
        └── data
            ├── chunks_head
            ├── lock
            ├── queries.active
            └── wal
                └── 00000000</code></pre><h2 id="node-exporter-설치">Node Exporter 설치</h2>
<blockquote>
<p>Node Exporter는 서버의 상태를 수집하여 Prometheus에 전달하는 역할을 한다.</p>
</blockquote>
<h3 id="파일로-직접-설치">파일로 직접 설치</h3>
<blockquote>
<h1 id="파일-다운로드">파일 다운로드</h1>
<p>wget <a href="https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz">https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz</a></p>
<p><strong># 압축 해제</strong></p>
<p>tar xvfz node_exporter-1.7.0.linux-amd64.tar.gz</p>
<p><strong># 폴더 접근</strong></p>
<p>cd node_exporter-1.7.0.linux-amd64/</p>
<p><strong># 서비스 파일 생성</strong></p>
<p>sudo vi /etc/systemd/system/node_exporter.service</p>
</blockquote>
<pre><code>[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=root
Group=root
Type=simple
ExecStart=/home/ubuntu/node-exporter/node_exporter-1.7.0.linux-amd64/node_exporter

[Install]
WantedBy=multi-user.target</code></pre><blockquote>
<h1 id="서비스-등록-및-시작">서비스 등록 및 시작</h1>
<p>sudo systemctl daemon-reload</p>
<p>sudo systemctl enable node_exporter.service</p>
<p>sudo systemctl start node_exporter.service</p>
</blockquote>
<h3 id="도커로-설치"><strong>도커로 설치</strong></h3>
<blockquote>
<h1 id="폴더-생성">폴더 생성</h1>
<p>mkdir node-exporter</p>
<p><strong># docker-compose 파일 생성</strong></p>
<p>vim docker-compose.yaml</p>
</blockquote>
<pre><code>version: &#39;3.8&#39;
services:
  node-exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    restart: unless-stopped
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - &#39;--path.procfs=/host/proc&#39;
      - &#39;--path.rootfs=/rootfs&#39;
      - &#39;--path.sysfs=/host/sys&#39;
      - &#39;--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)&#39;
    ports:
      - 9100:9100
</code></pre><blockquote>
<h4 id="어플리케이션-실행">어플리케이션 실행</h4>
<p>docker-compose up -d</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 기반 Airflow 구축]]></title>
            <link>https://velog.io/@nowod_it/Docker-%EA%B8%B0%EB%B0%98-Airflow-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@nowod_it/Docker-%EA%B8%B0%EB%B0%98-Airflow-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Tue, 21 May 2024 05:30:17 GMT</pubDate>
            <description><![CDATA[<h2 id="리눅스-환경-세팅">리눅스 환경 세팅</h2>
<h3 id="파이썬-설치">파이썬 설치</h3>
<blockquote>
<p>sudo apt update</p>
<p>sudo apt install software-properties-common</p>
<p>sudo add-apt-repository ppa:deadsnakes/ppa</p>
<p>sudo apt install python3.8</p>
<p><strong>[추가 파이썬 패키지]</strong></p>
<p>pip install apache-airflow</p>
<p>pip install boto3</p>
<p>mysql관련 패키지 설치시 사전 작업 :</p>
<p>sudo apt install default-libmysqlclient-dev pkg-config -y</p>
<p>pip install apache-airflow-providers-mysql</p>
<p>pip installapache-airflow-providers-oracle</p>
<p>pip install pandas</p>
</blockquote>
<h3 id="서버-시간-변경">서버 시간 변경</h3>
<blockquote>
<p>sudo timedatectl set-timezone Asia/Seoul</p>
</blockquote>
<h3 id="도커-설치"><strong>도커 설치</strong></h3>
<blockquote>
<p><strong>[우분투 시스템 패키지 업데이트]</strong></p>
<p>sudo apt-get update</p>
<p><strong>[필수 패키지 설치]</strong></p>
<p>sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common</p>
<p><strong>[Docker 공식 GPG키 추가]</strong></p>
<p>curl -fsSL <a href="https://download.docker.com/linux/ubuntu/gpg">https://download.docker.com/linux/ubuntu/gpg</a> | sudo apt-key add -</p>
<p><strong>[Docker 공식 apt 저장소 추가]</strong></p>
<p>sudo add-apt-repository &quot;deb [arch=amd64] <a href="https://download.docker.com/linux/ubuntu">https://download.docker.com/linux/ubuntu</a> $(lsb_release -cs) stable&quot;</p>
<p><strong>[Docker 설치]</strong></p>
<p>sudo apt-get install docker-ce docker-ce-cli containerd.io</p>
</blockquote>
<h3 id="docker-compose-설치">docker compose 설치</h3>
<blockquote>
<p>sudo curl -L <a href="https://github.com/docker/compose/releases/latest/download/docker-compose-$">https://github.com/docker/compose/releases/latest/download/docker-compose-$</a>(uname -s | tr &#39;[:upper:]&#39; &#39;[:lower:]&#39;)-$(uname -m) -o /usr/bin/docker-compose &amp;&amp; sudo chmod 755 /usr/bin/docker-compose &amp;&amp; docker-compose --version</p>
<p>[설치 후 확인]</p>
<p>sudo systemctl status docker</p>
<p>sudo systemctl start docker</p>
<p>sudo systemctl stop docker</p>
<p>sudo systemctl restart docker</p>
</blockquote>
<h3 id="docker-그룹에-user-추가">Docker 그룹에 User 추가</h3>
<blockquote>
<p>sudo groupadd docker</p>
<p>sudo usermod -aG docker $USER</p>
<p>[적용된 유저 바로 적용]</p>
<p>newgrp docker</p>
</blockquote>
<h2 id="airflow-설치---docker-compose-기반">Airflow 설치 - Docker Compose 기반</h2>
<h3 id="airflow---docker-compose-다운로드">airflow - docker compose 다운로드</h3>
<blockquote>
<p>curl -LfO &#39;<a href="https://airflow.apache.org/docs/apache-airflow/2.7.2/docker-compose.yaml&#39;">https://airflow.apache.org/docs/apache-airflow/2.7.2/docker-compose.yaml&#39;</a></p>
</blockquote>
<h3 id="airflow-구동">airflow 구동</h3>
<blockquote>
<p>[Airflow 부팅을 위한 DB 준비 (postgre, redis)]</p>
<p>sudo docker-compose up airflow-init</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/1e2eadc7-5fe4-4f90-8324-e62bd4adfacb/image.png" alt=""></p>
<blockquote>
<p>[Airflow 실행]</p>
<p>sudo docker-compose up -d</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/ac65b99c-c040-45e5-a31b-34668564108c/image.png" alt=""></p>
<h3 id="설정-관련-주요-변경사항-정리">설정 관련 주요 변경사항 정리</h3>
<ul>
<li>airflow Dockerfile 세팅</li>
</ul>
<blockquote>
<p>pip 패키지 관리를 위하여 requirements.txt 생성</p>
<p>custom 이미지 생성을 위하여 Dockerfile 세팅 (Docker-compose 내의 image → build: . 로 변경)</p>
</blockquote>
<ul>
<li><strong>airflow.cfg 연동</strong></li>
</ul>
<blockquote>
<p>docker-compose.yaml 파일 내 volumes 부분 수정</p>
<p>폴더별 맵핑 폴더 지정 및 airflow.cfg 파일 마운트</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/3fcdf9a8-41fa-47a5-ad3d-20337a40a047/image.png" alt=""></p>
<ul>
<li><strong>airflow.cfg 관련 수정 (연동 후)</strong></li>
</ul>
<blockquote>
<ol>
<li>default_timezone 변경 : utc → Asia/Seoul</li>
</ol>
<ul>
<li>airflow 구동 시 웹서버상의 시간 조정 (DAG에는 적용 안됨, 따로 적용해야함)</li>
</ul>
</blockquote>
<blockquote>
<ol start="2">
<li>load_examples 변경 : True → False</li>
</ol>
<ul>
<li>airflow 내부 샘플 DAGS를 보여주지 않도록 조정</li>
</ul>
</blockquote>
<blockquote>
<ol start="3">
<li>dag_dir_list_interval 변경 : 300 → 60</li>
</ol>
<ul>
<li>로컬에서 변경된 Dag 파일을 불러오는 시간 조정 5분 → 1분</li>
</ul>
</blockquote>
<h2 id="dag-파일-내-로컬-타임존-적용-방법">DAG 파일 내 로컬 타임존 적용 방법</h2>
<ul>
<li>파이썬 패키지 중 pendulum 사용 (pendulum은 airflow 설치 시 내장되어 있음)</li>
</ul>
<pre><code>from datetime import datetime
import pendulum

local_tz = pendulum.timezone(&quot;Asia/Seoul&quot;)

default_args = {
    ...,
    &quot;start_date&quot;: datetime(2022,6,7, tzinfo=local_tz),
    ...
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Github, AWS Code Deploy 기반 간단한 CI/CD 구축]]></title>
            <link>https://velog.io/@nowod_it/Github-AWS-Code-Deploy-%EA%B8%B0%EB%B0%98-%EA%B0%84%EB%8B%A8%ED%95%9C-CICD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@nowod_it/Github-AWS-Code-Deploy-%EA%B8%B0%EB%B0%98-%EA%B0%84%EB%8B%A8%ED%95%9C-CICD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Tue, 21 May 2024 05:25:19 GMT</pubDate>
            <description><![CDATA[<h2 id="cicd-파이프라인-구성"><strong>CI/CD 파이프라인 구성</strong></h2>
<h3 id="1-구성도">1. 구성도<img src="https://velog.velcdn.com/images/nowod_it/post/da2f43f4-68fd-4e71-b0b8-2679a4991bd6/image.png" alt=""></h3>
<h3 id="2-git--github"><strong>2. Git : Github</strong></h3>
<blockquote>
<p>원래 Gitlab으로 진행을 하려했으나, 리소스를 최소화 하기 위해 Github를 사용.</p>
<p>Github Organization을 사용하여 Private Repository 생성</p>
</blockquote>
<h3 id="3-cicd--github-action-aws-code-deploy"><strong>3. CI/CD : Github Action, AWS Code Deploy</strong></h3>
<blockquote>
<p>Github에서 제공하는 Github Action을 통해 프로젝트를 Build 하고 Build된 소스를 S3에 zip파일 형태로 업로드</p>
<p>이후 Github Action에서 Code Deploy 호출하여 Code Deploy에서 배포 진행.</p>
</blockquote>
<h2 id="단계별-설명"><strong>단계별 설명</strong></h2>
<h3 id="1-local-→-github-소스-push">1. local → Github 소스 Push</h3>
<ul>
<li>개발 진행 시 Issue 생성 후 Branch를 하나 생성한 뒤 로컬 개발 진행</li>
<li>로컬에서 개발 완료 후 Github으로 push</li>
<li>해당 Issue에 대하여 Pull Requsest</li>
</ul>
<h3 id="2-github-소스-merge-후-cicd-자동-시작">2. Github 소스 Merge 후 CI/CD 자동 시작</h3>
<ul>
<li>CI/CD 시작 (Repository 내부 .github/workflows/deploy.yml 에 정의된 job에 따라 수행)</li>
<li>Checkout → zip Create → S3 Upload → Code Deploy 실행 → install, build 수행</li>
</ul>
<h2 id="관련-문서"><strong>관련 문서</strong></h2>
<p><a href="https://blog.bespinglobal.com/post/github-action-%EC%9C%BC%EB%A1%9C-ec2-%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0/">https://blog.bespinglobal.com/post/github-action-%EC%9C%BC%EB%A1%9C-ec2-%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring ssh 터널링 에러 해결 (JSCH)]]></title>
            <link>https://velog.io/@nowod_it/Spring-ssh-%ED%84%B0%EB%84%90%EB%A7%81-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-JSCH</link>
            <guid>https://velog.io/@nowod_it/Spring-ssh-%ED%84%B0%EB%84%90%EB%A7%81-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-JSCH</guid>
            <pubDate>Tue, 21 May 2024 05:21:50 GMT</pubDate>
            <description><![CDATA[<h3 id="ssh-키로-서버에-연결-시-auth-fail-오류가-발생하는-경우-해결-방법"><strong>SSH 키로 서버에 연결 시 Auth Fail 오류가 발생하는 경우 해결 방법</strong></h3>
<hr>
<blockquote>
<p>JSCH에서 Private Key를 활용해 서버에 연결하는 과정에서 &quot;Auth Fail&quot; 오류가 발생하는 경우가 있습니다.</p>
<p>JSCH에서 Private Key로 통신할 때 ssh+rsa 방식을 사용하고 있는데, 이는 오래된 방식이라 최신의 ssh에는 적용되지 않는 경우가 많습니다.</p>
<p>이를 해결하기 위한 방법입니다.</p>
</blockquote>
<hr>
<h3 id="ssh-서버-설정-파일sshd_config-열기">SSH 서버 설정 파일(sshd_config) 열기:</h3>
<ul>
<li>SSH 서버의 설정 파일인 sshd_config를 수정해야 합니다. 다음 명령어를 사용하여 해당 파일을 엽니다.</li>
<li>sudo vim /etc/ssh/sshd_config</li>
</ul>
<h3 id="pubkeyacceptedalgorithms-설정-확인">PubkeyAcceptedAlgorithms 설정 확인</h3>
<ul>
<li>ssh+rsa를 적용하기 위해서 PubkeyAcceptedAlgorithms 설정이 존재하는지 확인합니다.</li>
<li>없다면 PubkeyAcceptedAlgorithms=+ssh-rsa 를 추가해줍니다.</li>
</ul>
<h3 id="서버-재시작">서버 재시작:</h3>
<ul>
<li>변경 사항을 적용하기 위해 SSH 서버를 재시작합니다.</li>
<li>sudo service ssh restart</li>
</ul>
<hr>
<p>이제 SSH 서버의 설정 파일을 수정하여 SSH 키 기반의 인증(Auth Fail 오류) 문제를 해결했습니다. 이제 다시 SSH 연결을 시도하면 정상적으로 접속될 것입니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Data Engineering] AWS Glue와 AWS RDS 연결 (PostgreSQL)]]></title>
            <link>https://velog.io/@nowod_it/Data-Engineering-AWS-Glue%EC%99%80-AWS-RDS-%EC%97%B0%EA%B2%B0-PostgreSQL</link>
            <guid>https://velog.io/@nowod_it/Data-Engineering-AWS-Glue%EC%99%80-AWS-RDS-%EC%97%B0%EA%B2%B0-PostgreSQL</guid>
            <pubDate>Thu, 01 Jun 2023 00:48:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/nowod_it/post/0dca3425-1f76-441e-980d-6878114ff91f/image.png" alt=""></p>
<h2 id="aws-glue란">AWS Glue란?</h2>
<ul>
<li>AWS Glue는 완전관리형 ETL(Extract, Transform, Load) 서비스로, 데이터 웨어하우스 또는 데이터 레이크에서 데이터 추출, 변환 및 로딩 작업을 자동화해주는 도구입니다. Glue는 스키마 추론, 데이터 카탈로그 생성, 스케일링 가능한 작업 실행 등을 통해 데이터 처리를 간편하게 해주며, 다양한 데이터 소스와 호환되는 연결 기능을 제공합니다.</li>
</ul>
<h3 id="도입이유">도입이유</h3>
<p>내부 데이터 플랫폼 구축사업의 일환으로 데이터 웨어하우스 구축과 데이터 마트 구축을 진행하고 있습니다. 데이터 웨어하우스에 구축된 데이터들을 분석용 데이터로 변환하고 데이터마트로 적재하기 위해서 AWS Glue를 도입하게 되었습니다.</p>
<h2 id="aws-glue와-aws-rds-연결하기">AWS Glue와 AWS RDS 연결하기</h2>
<p>AWS Glue와 RDS를 연결하기 위해서는 connection을 추가해주어야 합니다.. Data Catalog 탭에서 Connections 메뉴로 들어가면 connection을 추가해줄 수 있습니다. </p>
<p>(AWS RDS는 생성이 되어있다고 가정합니다.)</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/6aba8fb9-4b7d-4f9d-b2ca-ff5876afd813/image.png" alt=""></p>
<ol>
<li>Connection 만들기 <ol>
<li>Connections 탭에서 Create connection을 클릭합니다. </li>
</ol>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/250b430a-fcbb-44cb-b79f-582fee74a899/image.png" alt=""></p>
<ol start="2">
<li>이름을 설정하고 Connection Type을 설정합니다. <ol>
<li>DB정보와 URL을 알고있으면 JDBC로 해도되지만, 저는 AWS RDS를 쓰고 있으므로 Amazon RDS를 선택하겠습니다. (그러면 필요한 정보를 자동으로 맵핑해줍니다.)</li>
</ol>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/606692cf-15af-4c2b-88df-9dce864c4431/image.png" alt=""></p>
<ol start="3">
<li>데이터베이스 엔진을 선택합니다.  </li>
</ol>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/bc59ba30-76ad-41a0-9774-2148a414b8b5/image.png" alt=""></p>
<ol start="4">
<li>현재 만들어져있는 RDS 목록이 DB 인스턴스에 나타나고, 원하는 정보를 선택합니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/85146359-4250-4836-a619-91e3e272d16b/image.png" alt=""></p>
<ol start="5">
<li>접속할 DB명, User, Password 정보를 입력합니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/09154450-2f06-4ece-b031-4909b007ceb8/image.png" alt=""></p>
<ol start="6">
<li>생성이 완료되면 커넥션이 생성되고, 관련 정보들을 볼 수 있습니다.<ol>
<li>url은 jdbc 기반으로 자동 맵핑되어 생성됩니다.</li>
<li>또한 Amazon RDS를 선택하였기 때문에 VPC와 Subnet, 보안그룹도 자동으로 설정됩니다.</li>
</ol>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/f02b321d-2b64-4ae1-a6bd-477fa75cf015/image.png" alt=""></p>
<h2 id="연결-시-에러-대응-방법">연결 시 에러 대응 방법</h2>
<p>Connection이 생성되면 Test Connection을 통해서 연결테스트를 할 수 있는데, 이때 나올 수 있는 관련 에러들에 대해 공유해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/5d3a4afd-a410-45d0-8d8b-53eabdf4c4c4/image.png" alt=""></p>
<ol>
<li>권한 관련 에러<blockquote>
<ul>
<li>AWS Glue에서 커넥션 생성 후 테스트를 할 때 Glue 관련 IAM 설정이 필요합니다.</li>
</ul>
</blockquote>
<ul>
<li>RDS에대한 접근권한과 S3에 대한 접근권한이 필요하여 해당 권한을 추가해주었습니다.</li>
</ul>
</li>
<li>S3 Endpoint 관련 에러<blockquote>
<ul>
<li>이유는 명확하게 알 수 없지만,  VPC에 S3 endpoint가 설정이 되어야 테스트가 가능합니다.</li>
</ul>
</blockquote>
<ul>
<li>Gateway 유형의 S3 Endpoint를 생성하여 VPC에 추가해주었습니다.
<img src="https://velog.velcdn.com/images/nowod_it/post/c701c595-7bcf-4bca-93fd-86598037376f/image.png" alt=""></li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>가용영역(AZ) 관련 에러</p>
<blockquote>
<ul>
<li>Connection 생성 시 Network Option에서 Subnet을 설정할 수 있는데 이 때 가용영역이 아닌 영역을 선택하면 에러가 발생합니다.</li>
</ul>
</blockquote>
<ul>
<li><p>저같은 경우는 a,b,c,d 중에 d를 선택했을 때 a,b,c 중에 하나를 선택하라고 해서 변경했습니다. </p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/8209e06c-a455-4229-b97d-c82930b39ebd/image.png" alt=""></p>
</li>
</ul>
</li>
</ol>
<ol start="4">
<li><strong>** 데이터베이스 엔진관련 에러 **</strong> <blockquote>
<ul>
<li>제일 원인 파악이 어려웠던 에러입니다. (너무너무 화가 나더라구요)</li>
</ul>
</blockquote>
<ul>
<li>저의 경우 PostgreSQL 14버전을 사용해서 초기 DB세팅을 하였는데, 위의 에러를 모두 해결했음에도 불구하고 계속 연결이 실패되었습니다. </li>
<li>DB버전을 15로 올려보았으나 에러가 역시나 발생되었고, 결국엔 13버전으로 다시 세팅하여 연결하였더니 정상적으로 연결이 되었습니다. </li>
</ul>
</li>
</ol>
<h2 id="cloudwatch-활용방법">CloudWatch 활용방법</h2>
<p>DB Connection 테스트 시 관련 로그는 CloudWatch에 모두 저장됩니다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/b037e165-2ec0-4d8d-81eb-07a38ae70b2c/image.png" alt=""></p>
<p>이런식으로 error, output 로그가 모두 쌓이는데 output로그를 보셔야 명확한 원인 파악이 되니 output 로그를 적극적으로 활용하시기 바랍니다. </p>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Spring DI 생성자 주입(Constructor Injection)과 필드 주입(@Autowired)]]></title>
            <link>https://velog.io/@nowod_it/Java-Spring-DI-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85%EA%B3%BC-Autowired%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@nowod_it/Java-Spring-DI-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85%EA%B3%BC-Autowired%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Wed, 15 Mar 2023 05:24:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Depndency Injection에 대해 알고싶다면? 
 1편을 먼저 봐주세요. - <a href="https://velog.io/@nowod_it/Java-Spring-Dependncy-Injection-DI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85">Spring DI 이해하기</a></p>
</blockquote>
<hr>
<h2 id="spring은-어떻게-di를-해줄까">Spring은 어떻게 DI를 해줄까?</h2>
<p> Spring이 관리하는 자바 객체를 Bean이라고 한다. 자바에서는 객체를 생성할 때 new 연산을 통해서 새로운 객체를 만들지만, Spring을 사용하면 Bean으로 등록된 객체들을 Spring이 관리한다.</p>
<p> Bean으로 관리할 객체들은 어노테이션을 통해서 Spring에게 인지를 시켜줄 수 있으며, 많이 사용되는 것은 @Component, @Service, @Controller, @Repository가 있다.</p>
<hr>
<h3 id="component">@Component</h3>
<ul>
<li>Class 레벨의 어노테이션으로 Spring은 @Component를 인식해서 자동으로 객체를 관리한다.</li>
<li>@Service, @Controller, @Repository는 모두 @Component를 기본으로 한다.</li>
</ul>
<hr>
<h3 id="controller-service-repository">@Controller, @Service, @Repository</h3>
<ul>
<li>Spring MVC 패턴에 기반한 어노테이션으로 각각 역할을 명시적으로 구분하기 위해서 사용된다.</li>
<li><strong>@Controller</strong> : controller 클래스로 인식하기 위한 어노테이션</li>
<li><strong>@Service</strong> : 비즈니스 로직을 담고있는 Service 클래스로 인식하기 위한 어노테이션</li>
<li><strong>@Repository</strong> : DB 접근을 하는 repository 클래스로 인식하기 위한 어노테이션</li>
</ul>
<hr>
<h2 id="spring-di-방법">Spring DI 방법</h2>
<p>Spring은 component scan을 통해 자신이 관리할 객체들을 scan하고 여러가지 방법으로 이를 객체에 주입(Injection)해준다.</p>
<hr>
<h3 id="필드-주입-autowired---field-injection">필드 주입 (@Autowired - Field Injection)</h3>
<pre><code class="language-java">@Component
class B {
    public funcB() {
        System.out.println(&quot;this is b&quot;);
    }
}</code></pre>
<pre><code class="language-java">class A {
    @Autowired
    private B b;

    public funcA() {
        this.b.funcB;
        ...
    }
}</code></pre>
<ul>
<li>B 클래스를 @Component 어노테이션을 주면 Spring은 이를 인식하여 관리한다. </li>
<li>A 클래스에서 @Autowired를 사용하여 B 클래스를 주입받고자 하면 Spring에서 자동으로 객체를 주입해준다.</li>
</ul>
<h3 id="필드-주입의-문제점">필드 주입의 문제점</h3>
<p>필드 주입은 구현이 간단하며, 에러가 있는 경우 빌드 시 Exception이 떨어진다. 그렇기에 실제로 크게 문제가 없어보인다.</p>
<p>하지만 테스트 코드를 작성해보면, 문제점이 드러난다.
위 A 클래스의 기능을 단위 테스트로 만들어보자.</p>
<pre><code class="language-java">A a = new A();
a.funcA(); // NullPointerException</code></pre>
<p>A 클래스는 B클래스를 주입받아야만 동작하는데, 테스트 코드에서 new 연산자로 생성해버리면, 자동으로 주입이 안되기에 에러가 발생한다.</p>
<p>그래서 생성자 주입을 권장한다.</p>
<hr>
<h3 id="생성자-주입-constructor-injection">생성자 주입 (Constructor Injection)</h3>
<pre><code class="language-java">@Component
class A {

  private final B b;

  public A(B b) {
    this.b = b;
  }

  public void funcA() {
    b.funcB();
  }
}</code></pre>
<p>얼핏 보면 필드 주입보다 코드가 복잡해진다. 흔히 말하는 boiler plate 코드로 보일 수 있다.</p>
<p>하지만 이 방법에는 굉장한 장점이 있다.</p>
<blockquote>
<ol>
<li><strong>class A는 class B가 있어야만 정상적으로 생성이 가능하다. 이는 필수적인 종속성을 강제하기에 객체를 안전하게 생성할 수 있다.</strong></li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li><strong>필수적인 종속성관계의 객체들을 공개적으로 보여줄 수 있다. A를 단위테스트에서 생성하는 경우 B클래스가 있어야만 생성할 수 있도록 빌드 레벨에서 보여줄 수 있다.</strong></li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li><strong>final 변수로 선언하면서, 객체의 변화를 막을 수 있다. 보다 안정적으로 사용할 수 있다.</strong></li>
</ol>
</blockquote>
<p>비록 코드가 조금 더 복잡해졌지만, 필드 주입보다 훨씬 안정적인 객체를 얻을 수 있고, 테스트하기도 더 쉬워졌다.</p>
<h3 id="lombok-사용하기">Lombok 사용하기</h3>
<p>생성자 주입의 복잡한 코드를 줄이는 방법으로 Lombok 라이브러리가 있다.</p>
<pre><code class="language-java">// 롬복 적용 방법
compileOnly &#39;org.projectlombok:lombok:1.18.26&#39;
annotationProcessor &#39;org.projectlombok:lombok:1.18.26&#39;

testCompileOnly &#39;org.projectlombok:lombok:1.18.26&#39;
testAnnotationProcessor &#39;org.projectlombok:lombok:1.18.26&#39;</code></pre>
<h4 id="requiredargsconstructor">@RequiredArgsConstructor</h4>
<p>Lombok을 적용한 후 위 @RequiredArgsConstructor
어노테이션을 활용하면 이렇게 코드가 바뀐다. 지금은 주입되는 객체가 한개뿐이지만 여러개인 경우 코드가 엄청나게 간소해진다.</p>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
class A {

  private final B b;

  public void funcA() {
    b.funcB();
  }
}</code></pre>
<p>Lombok에는 이것 외에도 @Get, @Set 등등 다양한 활용방법이 있다.</p>
<hr>
<h3 id="결론">결론</h3>
<blockquote>
<p>생성자 주입을 활용하고, Lombok을 통해 코드를 간소화 하자!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Spring Dependncy Injection (DI, 의존성 주입) ]]></title>
            <link>https://velog.io/@nowod_it/Java-Spring-Dependncy-Injection-DI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@nowod_it/Java-Spring-Dependncy-Injection-DI-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Wed, 15 Mar 2023 01:48:42 GMT</pubDate>
            <description><![CDATA[<h2 id="dependency-injection">Dependency Injection</h2>
<blockquote>
<p>Dependency injection (DI) is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. </p>
<p><a href="https://docs.spring.io/spring-framework/">https://docs.spring.io/spring-framework/</a></p>
</blockquote>
<p>위 내용은 공식 홈페이지에서 설명하고 있는 DI(의존성 주입)의 정의다. 해당 내용은 번역하면 다음과 같다.</p>
<blockquote>
<p>&quot;객체가 자신의 종속성을 정의하는 프로세스이다. 즉 객체들은 생성자 인자, 팩토리 인자 등 팩토리 메서드를 통해 반환된 속성을 통해서만 작업할 수 있다.&quot; </p>
</blockquote>
<p>이게 도대체 무슨 말인가 싶겠지만, 하나씩 이해해보자.</p>
<h3 id="종속성">종속성</h3>
<ul>
<li><p>Java에서 종속성이란 클래스간의 의존관계를 뜻한다.
아래 코드를 보면 A 클래스의 함수를 실행하기 위해서는 B클래스를 필요로 한다.</p>
</li>
<li><p>만약에 B 클래스에 변화가 생기면 이는 A클래스에도 영향을 미치게 되며, 이것이 바로 종속성이다.</p>
<pre><code class="language-java">class A {
  private B b;

  public A() {
      this.b = new B();
  }

  public funcA() {
      this.b.funcB();
  }
}</code></pre>
</li>
</ul>
<h3 id="팩토리-메소드">팩토리 메소드</h3>
<blockquote>
<p>자세한 내용을 원하신다면 여기로 (설명이 정말 잘 되어있습니다.)
<a href="https://refactoring.guru/ko/design-patterns/factory-method">https://refactoring.guru/ko/design-patterns/factory-method</a></p>
</blockquote>
<p>팩토리 메소드는 객체를 구현하는 일종의 디자인 패턴이다.</p>
<ul>
<li>예를 들어 커피를 만드는 A 공장과 B 공장이 있다고하자. </li>
<li><span style="color:pink">A 공장에는 커피 원액 추출, 커피 만들기, 커피 담기 등의 공정이 있다. (커피에 최적화 되어있다.)</span></li>
<li><span style="color:pink">어느날 공장 규모를 키우기 위해 A공장을 이용해 오렌지 쥬스를 만들기로 했다. </span></li>
<li><span style="color:pink">그런데 A 공장은 커피만 생각하고 있었기에, 오렌지 쥬스를 위한 새로운 시설을 다 만들어야 한다.</span></li>
<li><span style="color:#FFD68A">*<em>B 공장은 이런 상황을 대비해 재료만 달라지면 다른 음료가 나오도록 공장을 설계했다. *</em></span></li>
<li><span style="color:#FFD68A"><strong>B 공장은 원액 추출, 음료 만들기, 음료 담기의 틀만 만들어놓고 재료만 넣으면 그 재료에 해당되는 음료가 나온다.</strong></span></li>
</ul>
<p>A공장의 경우 쥬스를 만들기 위해 처음부터 다시 모든 공정을 만들어야 하지만, B공장은 재료에 대한 처리만 해주면 바로 쥬스를 만들 수 있다.</p>
<p>B공장의 경우가 바로 팩토리 패턴을 사용하였기에, 변화에 열려있고 확장이 가능하다.</p>
<h3 id="해석">해석</h3>
<blockquote>
<p>&quot;객체가 자신의 종속성을 정의하는 프로세스이다. 즉 객체들은 생성자 인자, 팩토리 인자 등 팩토리 메서드를 통해 반환된 속성을 통해서만 작업할 수 있다.&quot;</p>
</blockquote>
<p>위 정의를 해석해보면 </p>
<ul>
<li>여러가지 객체를 가진 하나의 객체를 생성할 때, </li>
<li>팩토리 패턴(변화에 열려있고, 확장이 가능한)을 사용해서 만들어진 객체들을 통해서만 작업하도록 한다.</li>
<li>그렇게 만들어진 객체는 하위 객체들의 변화에도 크게 수정을 할 필요가 없고 안정적이다.</li>
<li>팩토리 패턴으로 만들어진 객체는 외부에서 의존성이 주입되기에 이를 제어의역전(IOC)라고 한다.</li>
</ul>
<p>이제 조금 이해가 가는 것 같다. 그런데 그렇다면 우리는 이 의존성을 주입하기 위해서 복잡한 팩토리 패턴의 코드를 일일히 개발해야 하는 것일까?</p>
<p>우리의 복잡한 이 마음을 바로 스프링(Spring)을 사용하여 평온을 만들 수 있다.</p>
<blockquote>
<p><a href="https://velog.io/@nowod_it/Java-Spring-DI-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85%EA%B3%BC-Autowired%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85">Spring 의존성 주입 알아보기</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Springboot에서 Swagger 3.0 적용하기]]></title>
            <link>https://velog.io/@nowod_it/Java-Springboot%EC%97%90%EC%84%9C-Swagger-3.0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@nowod_it/Java-Springboot%EC%97%90%EC%84%9C-Swagger-3.0-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 06 Mar 2023 15:16:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/nowod_it/post/26722636-e83e-49e2-833f-8e3c21b252e4/image.png" alt=""></p>
<h2 id="swagger란">Swagger란?</h2>
<blockquote>
<p>Simplify API development for users, teams, and enterprises with the Swagger open source and professional toolset. Find out how Swagger can help you design and document your APIs at scale.
출처 : <a href="https://swagger.io/">https://swagger.io/</a> </p>
</blockquote>
<p><strong><em>스웨거는 Rest API를 설계하고, 개발하고, 테스트하는데 도움을 주는 Tool 입니다.</em></strong></p>
<p>일반적으로 Swagger를 통해서 API 문서를 만들고, API 테스트도 진행을 하는 용도로 사용되며, 더불어 API 설계, 개발, 모니터링까지 다양한 기능을 제공한다.</p>
<h2 id="springboot-버전">Springboot 버전</h2>
<ul>
<li>권장 Springboot 버전 : 2.7.9</li>
<li>적용할 Swagger 버전 : springfox 3.0</li>
</ul>
<blockquote>
<p><span style="background-color:yellow; color:black;"><strong>들어가기에 앞서... Springboot 3.0 대의 버전은 Swagger 적용 시 어려움이 많다. 특히 &quot;Unable to infer base url.&quot; 아무리 잡아보려고 해도 잡히지가 않으니, Springboot 2.0대 버전을 권장한다.!!!!!!!! 나는 2.7.9 버전을 사용했다.</strong></span></p>
</blockquote>
<p>[해결하지 못한 에러..]
<img src="https://velog.velcdn.com/images/nowod_it/post/a1a084f9-b3e6-4b9a-b2df-4953b2b83403/image.png" alt=""></p>
<h2 id="springboot에-swagger-적용하기">Springboot에 Swagger 적용하기</h2>
<ol>
<li>먼저 build.gradle에 아래 2개의 라이브러리를 적용한다.</li>
</ol>
<pre><code class="language-java">// build.gradle 적용
implementation &#39;io.springfox:springfox-boot-starter:3.0.0&#39;
implementation &#39;io.springfox:springfox-swagger-ui:3.0.0&#39;</code></pre>
<ol start="2">
<li>SwaggerConfig 파일 작성</li>
</ol>
<ul>
<li>SpringBoot를 사용하는 경우 cofig 파일의 annotation을 @Configuration만 작성하면 된다.</li>
</ul>
<pre><code class="language-java">@Configuration
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(&quot;Swagger 문서 제목&quot;)
                .description(&quot;Swagger 문서 설명&quot;)
                .version(&quot;1.0&quot;)
                .build();
    }
}</code></pre>
<ul>
<li>만약 기본 Spring을 사용한다면 @Configuration과 @EnableSwagger2를 사용해주어야 한다.<pre><code class="language-java">@Configuration
@EnableSwagger2
public class SwaggerConfig {                             
  //...code
}</code></pre>
</li>
</ul>
<ol start="3">
<li>application.yml 설정 </li>
</ol>
<ul>
<li>아래 설정을 추가해준다.</li>
</ul>
<pre><code class="language-yml">    mvc:
        pathmatch:
            matching-strategy: ant_path_matcher</code></pre>
<p>기본적으로 Gradle 라이브러리 추가와 설정만 추가해준다면 자연스럽게 Swagger가 적용된다.</p>
<p>아래 Swagger 3.0의 접속 주소로 접속하면 Swagger가 뜨는 것을 볼 수 있다.</p>
<blockquote>
<p>Swagger 3.0 접속 주소 : <a href="http://localhost:8080/swagger-ui/">http://localhost:8080/swagger-ui/</a> 
Swagger 2.0 접속 주소 : <a href="http://localhost:8080/swagger-ui.html">http://localhost:8080/swagger-ui.html</a></p>
</blockquote>
<p>만약 적용을 했는데도 에러가 나는 경우는 Spring Security 설정이나, Springboot 버전 문제일 확률이 높다.</p>
<p>적용이 잘 되었다면 아래와 같이 정상적으로 작동한다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/fb231431-67e7-4177-819a-27fa2f5c3c18/image.png" alt=""></p>
<h2 id="문서-정규화-하기">문서 정규화 하기</h2>
<p>기본적으로 controller에 대한 이름, 메서드 정의, In/out에 대한 정의를 할 수 있다.</p>
<ul>
<li>@Api(tags = &quot;&quot;) : 해당 컨트롤러의 API 명을 지정합니다.</li>
<li>@Operation(summary = &quot;&quot;, description = &quot;&quot;) : 특정 API의 요약과 설명을 지정합니다.</li>
<li>@ApiResponse : API 결과에 따른 코드와 설명을 추가합니다.</li>
</ul>
<pre><code class="language-java">@Api(tags = &quot;Some API&quot;)
@RestController
@RequestMapping(&quot;/api/some&quot;)
public class SomeTestRestController {

    @Operation(summary = &quot;Some API&quot;, description = &quot;설명&quot;)
    @ApiResponses(value = {
            @ApiResponse(responseCode = &quot;success&quot;, description = &quot;성공&quot;),
            @ApiResponse(responseCode = &quot;fail&quot;)})
    @PostMapping(path=&quot;payment&quot;)
    public String someAPI(@RequestBody SomeData somedata){
        return &#39;&#39;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/bdfd40b0-c227-4f92-8f4c-fbe1c68f7ba5/image.png" alt=""></p>
<blockquote>
<p>적용자체는 간단하지만, Springboot 3.0.4에 적용하려고 하다가 몇시간을 날려먹었다. 여러분은 고생하시지말고 꼭 안정적인 Springboot 2.0대의 버전을 사용하길 바랍니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Spring Webflux 간단 후기 (with 공홈 getting started)]]></title>
            <link>https://velog.io/@nowod_it/Java-Spring-Webflux-%EC%A0%95%EB%A6%AC-with-%EA%B3%B5%ED%99%88-getting-started</link>
            <guid>https://velog.io/@nowod_it/Java-Spring-Webflux-%EC%A0%95%EB%A6%AC-with-%EA%B3%B5%ED%99%88-getting-started</guid>
            <pubDate>Sun, 19 Feb 2023 14:20:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/nowod_it/post/81196906-be5f-44b7-bb2d-9f4b33981eee/image.png" alt=""></p>
<blockquote>
<p>참고자료 : <a href="https://reflectoring.io/getting-started-with-spring-webflux/">https://reflectoring.io/getting-started-with-spring-webflux/</a>
<a href="https://spring.io/guides/gs/reactive-rest-service/">https://spring.io/guides/gs/reactive-rest-service/</a></p>
</blockquote>
<h1 id="webflux란-무엇인가">Webflux란 무엇인가?</h1>
<hr>
<blockquote>
<p>The <span style="background-color:#fff5b1; color:black" ><strong>original web framework</strong></span> included in the Spring Framework, Spring Web MVC, was purpose-built for the Servlet API and Servlet containers. The <span style="background-color:#fff5b1; color:black" ><strong>reactive-stack web framework</strong></span>, Spring WebFlux, was added later in version 5.0. It is <span style="background-color:#fff5b1; color:black" ><strong>fully non-blocking, supports Reactive Streams back pressure,</strong></span> and runs on such servers as Netty, Undertow, and Servlet containers.</p>
</blockquote>
<p>공식 홈페이지에 따르면 Webflux는 non-blocking, reactive streams back pressure를 지원하는 스프링 프레임워크이다.</p>
<p>non-blocking 즉 비동기 방식을 지원한다는 것은 개념적으로 어렵지 않으나, 
reactive streams back pressure는 도대체 무슨말일까?</p>
<h3 id="reactive">Reactive</h3>
<p>리액티브는 하나의 프로그래밍 패러다임이라고 볼 수 있다. non-blocking, 비동기, 이벤트 기반, 메시지 기반의 데이터 처리를 한다. </p>
<p>기존의 Blocking 형식의 리퀘스트는 서버에 리퀘스트가 전송될 때 마다, 서블릿 스레드가 생성되고, 워커(worker) 스레드에 작업을 위임한다. 워커 스레드가 처리를 하는동안 서블릿 스레드는 응답을 기다린다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/dfc340a8-0b2f-42be-be25-79964410cf1c/image.png" alt=""></p>
<p>하지만 Non-Blockiing 리퀘스트의 경우 이벤트 핸들러와 콜백을 모든 요청에 포함한다. 요청 스레드는 스레드풀에 요청을 위임하고 다음 요청을 처리하는데 바로 사용될 수 있다. 핸들러 함수를 통해 요청이 완료되면 콜백함수에 전달한다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/b7da3342-22fc-4eb3-8b5d-90a253bd1cfb/image.png" alt=""></p>
<h3 id="stream">Stream</h3>
<p>스트림은 시스템으로 전송되는 일련의 데이터라고 할 수 있다. java8에서도 stream API가 등장하였고, 배열이나 컬렉션같은 일련의 데이터를 순차적으로 처리할 수 있게 해준다.</p>
<h3 id="back-pressure">Back Pressure</h3>
<p>사전적인 의미는 &quot;흐름에 반대하는 저항 또는 힘&quot;이다. 프로그래밍에서는 데이터 전송을 규제하는 메커니즘이다. Reactive Stream에서는 publisher에서 subscriber로 데이터를 전송할 때 pub -&gt; sub으로 데이터를 push하는 것이 아닌, sub -&gt; pub으로 구독자가 요청한 만큼씩 pull하는 방식이다. </p>
<p>예를 들어 서버 A에서 B로 1000개의 초당 요청을 보낸다고 할 때, B가 처리할 수 있는 최대는 800이다. 그러면 200에 대한 데이터를 처리하지 못하기에 서버 메모리 부족으로 요청이 실패가 될 것이다. </p>
<p>하지만 Back Pressure 전략을 사용하면 이러한 처리를 제어할 수 있다.</p>
<blockquote>
<h3 id="종합해보면-reactive-streams-back-pressure-이란">종합해보면 reactive streams back pressure 이란?</h3>
<p>: Pub/Sub 구조로 연속된 데이터(요청)을 비동기적으로 처리하는 방식</p>
</blockquote>
<p>위 개념을 염두해두고 정말 간단한 Reactive RESTful Web Service를 만들어보자.</p>
<h1 id="간단한-reactive-web-service-만들어보기">간단한 Reactive Web Service 만들어보기</h1>
<p>먼저 <a href="https://start.spring.io/">https://start.spring.io/</a> 에서 Spring Reactive Web 디펜던시를 추가하여 프로젝트를 하나 만들자.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/bad3b074-6504-4acb-a3c7-84cac803eb0b/image.png" alt=""></p>
<p>먼저 POJO형식의 데이터 객체를 하나 만든다. </p>
<blockquote>
<p>참고로 POJO(Plained Old Java Object) 란? 
:getter, setter와 같은 기본적인 기능만 갖고 있는 가벼운 객체를 말한다.</p>
</blockquote>
<pre><code class="language-java">package hello;


public class Greeting {

  private String message;

  public Greeting() {
  }

  public Greeting(String message) {
    this.message = message;
  }

  public String getMessage() {
    return this.message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  @Override
  public String toString() {
    return &quot;Greeting{&quot; +
        &quot;message=&#39;&quot; + message + &#39;\&#39;&#39; +
        &#39;}&#39;;
  }
}</code></pre>
<p>그리고 요청을 처리할 핸들러를 만든다.
핸들러는 요청이 왔을 때 요청을 처리하는 역할을 한다.</p>
<pre><code class="language-java">package hello;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import reactor.core.publisher.Mono;

@Component
public class GreetingHandler {

  public Mono&lt;ServerResponse&gt; hello(ServerRequest request) {
    return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
      .body(BodyInserters.fromValue(new Greeting(&quot;Hello, Spring!&quot;)));
  }
}</code></pre>
<p>그리고 Router를 만든다.
라우터는 URI상의 path를 listen하고 핸들러에서 제공하는 값을 리턴한다.</p>
<pre><code class="language-java">package hello;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;

@Configuration(proxyBeanMethods = false)
public class GreetingRouter {

  @Bean
  public RouterFunction&lt;ServerResponse&gt; route(GreetingHandler greetingHandler) {

    return RouterFunctions
      .route(GET(&quot;/hello&quot;).and(accept(MediaType.APPLICATION_JSON)), greetingHandler::hello);
  }
}</code></pre>
<p>그리고 spring boot application을 실행할 어플리케이션 객체를 만들어서 실행한다.</p>
<pre><code class="language-java">package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
  }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/ae904031-861e-4313-b3c5-7f3dfe4d3aa2/image.png" alt=""></p>
<p>&quot;Hello, Spring!&quot;을 출력하는 Reactive WebService가 만들어졌다.</p>
<p>구조적으로 @Controller의 역할이 Router, Handler로 분리되었다고도 볼 수 있다.</p>
<p>그리고 Handler에서 Mono타입으로 값을 return 하는데, 이 Mono 타입이 webflux의 주요한 기능이라고 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MySQL] MySQL root 계정 재생성]]></title>
            <link>https://velog.io/@nowod_it/MySQL-MySQL-root-%EA%B3%84%EC%A0%95-%EC%9E%AC%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@nowod_it/MySQL-MySQL-root-%EA%B3%84%EC%A0%95-%EC%9E%AC%EC%83%9D%EC%84%B1</guid>
            <pubDate>Sat, 11 Feb 2023 10:21:59 GMT</pubDate>
            <description><![CDATA[<h2 id="데이터-마이그레이션-준비">데이터 마이그레이션 준비</h2>
<p>예전 회사에서 MySQL 버전 업그레이드 및 데이터 마이그레이션 관련 프로젝트를 진행했었다.</p>
<p>DB마이그레이션 사전작업을 준비하던 중 문제점이 하나 발생하였는데, 바로 DB root 계정이 없었던 것이다.</p>
<p>AWS에서 제공하는 마이그레이션 툴은 root계정을 필요로 하기에, root 계정을 재생성하기 위한 작업이 필요했다.</p>
<h2 id="전제조건">전제조건</h2>
<blockquote>
<p>&quot;DB의 재시작을 할 수 있는 조건이어야 한다. (운영 권한, 서비스 시간 고려)&quot;
&quot;OS의 루트 권한이 있어야한다. (DB설정 및 서비스 재시작이 필요)&quot;</p>
</blockquote>
<p>개인 프로젝트라면 전혀 문제가 없었겠지만, 안타깝게도 운영DB에 작업을 해야하다 보니 DB 서비스의 재시작은 굉장히 큰 리스크가 있었다.</p>
<p>그래서 결국 12시 이후 작업을 진행하기로 하였다.
물론 다행히도 리눅스 루트 권한은 있었다.</p>
<h2 id="root-계정-재생성">root 계정 재생성</h2>
<blockquote>
<p><strong>1. 설정파일을 열어 skip-grant-tables를 추가한다.</strong>
<em>&quot;sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf&quot;</em></p>
</blockquote>
<ul>
<li>skip-grant-tables를 추가하면 말그대로 grant-tables를 스킵한다.</li>
<li>시스템이 권한 없이 실행되는 것이다.</li>
</ul>
<blockquote>
<p>** 2. MySQL 재시작 **
&quot;sudo killall -9 mysqld&quot;
&quot;sudo service mysql restart&quot;</p>
</blockquote>
<blockquote>
<p><strong>3. root로 mysql 접속</strong> (skip-grant-tables 덕분에 가능)
&quot;sudo mysql -u root mysql&quot;</p>
</blockquote>
<blockquote>
<p>*<em>4. root 계정 생성 *</em>
&quot;insert into user (Host, User, Password) values (&#39;%&#39;, &#39;root&#39;, password(&#39;password));&quot;
&quot;insert into user (Host, User, Password) values (&#39;localhost&#39;, &#39;root&#39;, password(&#39;password&#39;));&quot;</p>
</blockquote>
<blockquote>
<p><strong>5. 변경 사항 적용 및 권한 추가</strong>
&quot;flush privileges;&quot;
&quot;grant all privileges on <em>.</em> to root@localhost  with grant option;&quot;
&quot;grant all privileges on <em>.</em> to root@&#39;%&#39;  with grant option;&quot;
&quot;flush privileges;&quot;</p>
</blockquote>
<blockquote>
<p>*<em>6. skip-grant-tables 삭제 후 재시작 *</em>
&quot;sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf&quot;
&quot;sudo service mysql restart&quot;</p>
</blockquote>
<p>사실 root을 삭제하는 것은 MySQL 사용할 때에는 보안적으로 굉장히 좋다.
갑자기 root계정이 필요해지는 순간에 이 글이 도움되기를 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Spring 프로퍼티 암복호화 - Jasypt 사용법 및 에러 해결]]></title>
            <link>https://velog.io/@nowod_it/Java-Spring-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%95%94%EB%B3%B5%ED%98%B8%ED%99%94-Jasypt-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%8F-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@nowod_it/Java-Spring-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%95%94%EB%B3%B5%ED%98%B8%ED%99%94-Jasypt-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%8F-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Fri, 03 Feb 2023 06:41:13 GMT</pubDate>
            <description><![CDATA[<h2 id="jasypt가-필요했던-이유">Jasypt가 필요했던 이유</h2>
<p>보통 Spring을 사용할 때 .properties 파일을 활용하여 웹어플리케이션을 구동하거나, DB 연결 등 필요한 정보들을 담아둔다.</p>
<p>개인적인 프로젝트나, 보안이 중요하지 않은 프로젝트에서는 그런 정보들을 관리할 필요가 없다. 하지만 고객정보나, 중요한 키값들이 담겨있는 경우에는 암호화가 필요하다. </p>
<p>이번에 주요정보 암복호화에 대한 요건이 생기면서, KMS(Key Managment System)을 통해 주요 키값을 가져와야 했다.</p>
<blockquote>
<p>KMS (Key Management System)이란?</p>
</blockquote>
<ul>
<li>암호화 키를 관리하는 시스템으로 키의 생성, 저장, 복구 등 키값의 라이프사이클을 관리하는 시스템이다.</li>
</ul>
<p>주요 키값을 가져오려면 키값을 확인하기 위한 파라미터들이 필요하다. 사실 키값을 가져오기 위한 파라미터도 주요 정보였고, 이 파라미터를 암복호화 하기 위해서 Jasypt를 사용하게 되었다.</p>
<h2 id="jasypt">Jasypt</h2>
<blockquote>
<p>Jasypt is a java library which allows the developer to add basic encryption capabilities to his/her projects with minimum effort, and without the need of having deep knowledge on how cryptography works.
출처 : <a href="http://www.jasypt.org/">http://www.jasypt.org/</a></p>
</blockquote>
<p>Jasypt는 암복호화를 깊이 이해하지 않더라도 개발자가 손쉽게 사용할 수 있도록 만들어진 라이브러리다. 공식 홈페이지를 가보면 Spring, hibernate와 같이 Spring에 특화되어서 만들어진 라이브러리 이기에 스프링을 사용하는 프로젝트라면 어디든 적용할 수 있다.</p>
<p>현재 적용할 프로젝트는 Spring 4로 되어있어서 문제없이 적용할 수 있었다.
Jasypt를 적용하는 흐름은 다음과 같다.</p>
<blockquote>
<ol>
<li>Jasypt 라이브러리 적용</li>
<li>Jasypt를 이용하여 암호화 및 프로퍼티 적용</li>
<li>Spring context.xml을 활용하여 프로퍼티 복호화</li>
</ol>
</blockquote>
<h2 id="jasypt-라이브러리-적용">Jasypt 라이브러리 적용</h2>
<p>라이브러리 적용은 간단하다. maven을 사용중이면 pom.xml을 통해 적용할 수도 있고, 라이브러리를 다운받아 import할 수도 있다.</p>
<p>jasypt와 jasypt-spring31 두개를 편한 방법으로 프로젝트에 적용하면 된다.</p>
<pre><code>&lt;!-- https://mvnrepository.com/artifact/org.jasypt/jasypt --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.jasypt&lt;/groupId&gt;
    &lt;artifactId&gt;jasypt&lt;/artifactId&gt;
    &lt;version&gt;1.9.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- https://mvnrepository.com/artifact/org.jasypt/jasypt-spring31 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.jasypt&lt;/groupId&gt;
    &lt;artifactId&gt;jasypt-spring31&lt;/artifactId&gt;
    &lt;version&gt;1.9.2&lt;/version&gt;
&lt;/dependency&gt;
</code></pre><h2 id="jasypt를-이용하여-암호화-및-프로퍼티-적용">Jasypt를 이용하여 암호화 및 프로퍼티 적용</h2>
<p>일단 Jasypt를 통해서 프로퍼티를 암호화하려면, 암호화 도구가 필요하다.
위 라이브러리로 코드를 짜서 암호화도 가능하지만 더 간단한 방법이 있다.</p>
<p><a href="https://github.com/jasypt/jasypt">https://github.com/jasypt/jasypt</a> </p>
<p>위 사이트에 접속해서 jasypt 1.9.3을 다운 받는다.
<img src="https://velog.velcdn.com/images/nowod_it/post/94023978-9362-44d6-a247-1e5655b8631c/image.png" alt=""></p>
<p>다운받고 나면 bin 폴더 밑에 encrypt.bat, encrypt.sh가 있는데 PC 환경에 따라 실행을 한 후에 암호화를 시작한다.</p>
<p><a href="https://www.devglan.com/online-tools/jasypt-online-encryption-decryption">https://www.devglan.com/online-tools/jasypt-online-encryption-decryption</a> &lt;- 이 사이트에서도 암호화가 가능하다.</p>
<pre><code>$ ./encrypt.sh input=&quot;This is my message to be encrypted&quot; password=MYPAS_WORD

 ----ENVIRONMENT-----------------

Runtime: Sun Microsystems Inc. Java HotSpot(TM) Client VM 1.6.0_03-b05



 ----ARGUMENTS-------------------

input: This is my message to be encrypted
password: MYPAS_WORD



 ----OUTPUT----------------------

k1AwOd5XuW4VfPQtEXEdVlMnaNn19hivMbn1G4JQgq/jArjtKqryXksYX4Hl6A0e</code></pre><p>쉘파일을 실행하면서 input, password를 인자로 주면 암호화가된다.
뒤에 알고리즘도 설정할 수 있는데 아무것도 설정하지 않으면 PBEWithMD5AndDES가 기본이다.</p>
<p>password 값은 복호화 할때도 사용되는 값이니, 기억해두자.</p>
<p>이렇게 암호화가 된 값을 이제 properties 파일에 적용하자.</p>
<pre><code>#예제 프로퍼티 파일
test.key=ENC(k1AwOd5XuW4VfPQtEXEdVlMnaNn19hivMbn1G4JQgq/jArjtKqryXksYX4Hl6A0e)
test.noKey=1234</code></pre><p>암호화가 되어있는 프로퍼티 값은 복호화 대상으로 잡기위해 앞에 ENC()를 붙인다.
참고로 복호화가 필요없으면 평문으로 하면된다.</p>
<p>여기까지 되었으면 암호화 및 프로퍼티 준비가 완료되었다.</p>
<h2 id="spring-contextxml-을-활용하여-프로퍼티-복호화">Spring context.xml 을 활용하여 프로퍼티 복호화</h2>
<p>스프링 설정은 xml을 활용하는 방법이나, java로 만드는 방법이 있다.
현재 프로젝트는 xml로 되어있어서 xml 기반으로 설명을 하겠다.</p>
<p>복호화에는 총 3가지 설정이 필요하다.</p>
<blockquote>
<ol>
<li>복호화를 위한 Password와 알고리즘 설정</li>
<li>properties 파일 불러오기 및 복호화</li>
<li>복호화된 값 사용하기</li>
</ol>
</blockquote>
<h4 id="1-복호화를-위한-password와-알고리즘-설정">1. 복호화를 위한 Password와 알고리즘 설정</h4>
<pre><code> &lt;bean id=&quot;environmentVariablesConfiguration&quot;
     class=&quot;org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig&quot;&gt;
   &lt;property name=&quot;algorithm&quot; value=&quot;PBEWithMD5AndDES&quot; /&gt;
   &lt;property name=&quot;passwordEnvName&quot; value=&quot;APP_ENCRYPTION_PASSWORD&quot; /&gt;
   &lt;!--&lt;property name=&quot;password&quot; value=&quot;APP_ENCRYPTION_PASSWORD&quot; /&gt;--&gt;
 &lt;/bean&gt;</code></pre><p>위 설정은 알고리즘과 password를 설정한다.
여기서 passwordEnvName은 환경변수에 password값을 가져오는 기능으로 password의 값을 WAS가 아닌 서버환경에서 가져오기 때문에 보안이 더욱 뛰어나다.</p>
<h4 id="2-properties-파일-불러오기-및-복호화">2. properties 파일 불러오기 및 복호화</h4>
<pre><code> &lt;bean id=&quot;configurationEncryptor&quot;
     class=&quot;org.jasypt.encryption.pbe.StandardPBEStringEncryptor&quot;&gt;
   &lt;property name=&quot;config&quot; ref=&quot;environmentVariablesConfiguration&quot; /&gt;
 &lt;/bean&gt;

  &lt;bean id=&quot;propertyConfigurer&quot;
     class=&quot;org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer&quot;&gt;
   &lt;constructor-arg ref=&quot;configurationEncryptor&quot; /&gt;
   &lt;property name=&quot;locations&quot;&gt;
     &lt;list&gt;
       &lt;value&gt;/WEB-INF/classes/application.properties&lt;/value&gt;
     &lt;/list&gt;
   &lt;/property&gt;
 &lt;/bean&gt;</code></pre><p>위 설정을 통해서 /WEB-INF/classes/application.properties 경로의 프로퍼티를 불러온다. 
참고로 혹시라도 스프링 설정에서 다른 porperty를 불러오는 설정이 있다면 에러가 발생하기에 확인해야한다. ex) PropertyPlaceholderConfigurer</p>
<p>우리 프로젝트의 경우 이미 사용되고 있는 PropertyPlaceholderConfigurer 설정이 있어서 해당 설정을 주석처리하고 EncryptablePropertyPlaceholderConfigurer만 사용하게 하였다.</p>
<h4 id="3-복호화된-값-사용하기">3. 복호화된 값 사용하기</h4>
<p>대부분 DB연결이 주목적으로 가이드가 많이 되어있는데, 이번 프로젝트의 경우에는 다른용도로 사용이 필요했기에, bean을 하나 만들어서 사용하였다.</p>
<pre><code>&lt;beans:bean class=&quot;test.com.kmsRequestDTO&quot;&gt;
    &lt;beans:property name=&quot;key&quot; value=&quot;${test.key}&quot;/&gt;
    &lt;beans:property name=&quot;noKey&quot; value=&quot;${test.noKey}&quot;/&gt;
&lt;/beans:bean&gt;</code></pre><p>이렇게 만들어놓고 필요할 때 @Autowired로 주입해서 사용하면 된다.</p>
<h2 id="jasypt-193-에러-버그-이슈">Jasypt 1.9.3 에러, 버그, 이슈</h2>
<p>이번 프로젝트에서 제일 화나게 했던 내용이다. Jasypt는 현재 1.9.3 버전을 마지막으로 업데이트가 되어있다. 그래서 1.9.3으로 개발을 진행하던 중 가져온 설정파일의 value 값이 공백으로 계속 들어오는 이슈가 있었다.</p>
<p>당연히 스프링 설정을 잘못한줄 알고 몇시간을 고생을 하였는데, 혹시나 해서 Jasypt 1.9.3을 디버깅해보니 소스에 오류가 있었다. 라이브러리를 잘못받았는지 사용을 잘못해서 그런지는 모르겠지만, 라이브러리를 안까봤으면 화병이 날뻔 했다.</p>
<p>문제의 원인은 Decrypt할 때 사용되는 StandardPBEByteEncryptor의 Decrypt 함수이다.</p>
<pre><code class="language-java">//문제의 1.9.3 decrypt 소스
System.arraycopy(encryptedMessageKernel, encMesKernelStart, finalEncryptedMessageKernel, 0, encMesKernelSize);</code></pre>
<p>위 소스는 encryptedMessageKernel배열을 encMesKernelStart 위치부터 finalEncryptedMessageKernel에 복사하는 코드이다.
그런데 encMesKernelStart의 값이 encryptedMessageKernel의 길이와 동일한 값이다.</p>
<p>그래서 finalEncryptedMessageKernel로 복사되는 값이 공백이 되어버렸다.</p>
<p>그래서 1.9.2로 다운그레이드를 해보니 위 문제되는 소스가 없었고, 정상적으로 값이 나왔다.</p>
<blockquote>
<h2 id="결론--jasypt는-192-버전을-사용하기를-바란다">결론 : Jasypt는 1.9.2 버전을 사용하기를 바란다.</h2>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로젝트] 법인 통합 프로젝트 회고]]></title>
            <link>https://velog.io/@nowod_it/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B2%95%EC%9D%B8-%ED%86%B5%ED%95%A9-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@nowod_it/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B2%95%EC%9D%B8-%ED%86%B5%ED%95%A9-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 28 Jan 2023 08:38:37 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트의-시작">프로젝트의 시작</h2>
<p>작년 이맘때쯤 재직중인 회사가 금융지주에 인수되면서, 많은 변화가 있었다.
대외적인 변화 뿐만 아니라, 시스템 내부적으로도 지주의 정책이나 이슈에 따라 많은 프로젝트들이 있었다. 내부 시스템 연동, 데이터센터 이전 굵직한 작업들이 참 많았다.</p>
<p>그러던 중 지주안에 있는 같은 비즈니스를 하고 있는 회사와의 통합얘기가 나오기 시작했고, 23년 1월을 목표로 양사의 법인을 통합하는 프로젝트가 시작되었다.</p>
<h2 id="프로젝트의-목표">프로젝트의 목표</h2>
<p>이번 통합프로젝트의 목표는 딱 하나였다. 23년 1월부로 대외적인 모든 것들이 통합법인으로 보여야 한다. 얼핏보면 간단해 보이는 일이였고, 그렇게 안일한 생각으로 프로젝트가 시작되었다.</p>
<p>그 중 3가지의 주요 목표가 있었다.</p>
<blockquote>
<ol>
<li>디자인 변경 (톤앤 매너)</li>
<li>사명, 도메인 변경</li>
<li>통합 업무 조회</li>
</ol>
</blockquote>
<p>제일 먼저 끝난건 디자인변경이였다. 사실 처음에는 디자인 원본 파일들의 행방을 알 수 없어, 굉장히 오래걸릴 것으로 예상했지만 다행히도 원본파일을 찾을 수 있었다.</p>
<p>그 다음으로 끝낼 수 있었던 건 통합 업무 조회였다. 이 작업은 양사 네트워크 환경, 개발환경 등등 기본적인 베이스 작업들이 굉장히 오래걸렸다. 약 2달정도 진행이 되었고 환경이 세팅된 이후에는 인터페이스 통합등 몇 가지 작업들을 통해 충분히 개발할 수 있었다.</p>
<p>이제 남은 것은 2번 사명, 도메인 변경이다. 그런데 정말 놀랍게도 이 작업이 제일 오래 걸렸다. 이유는 간단하다. <em><strong>하드코딩된 소스들이 정말 많았다는 것.</strong></em></p>
<h2 id="소스-공통화-및-관리의-중요성">소스 공통화 및 관리의 중요성</h2>
<p>적게는 5년, 많게는 10년이상 대규모 변화와는 관련없던 시스템들이다 보니, 소스 공통화나 관리들이 제대로 되고 있지 않았다.</p>
<p>여기저기 하드코딩된 소스들과 출처를 알 수 없는 소스들. 어디서도 사용되지 않아 보이지만 어디선가 사용되고 있는 그런 소스들. 말그대로 스파게티 코드의 집합체였다.</p>
<p>사실 이렇게 된 원인은 다양하다. 외주 인력이 많았다는 점, 대규모 프로젝트를 염두해두지 않았다는 점, 빡빡한 일정에 쫓겼을 수도 있다.</p>
<p>아무튼 그런 이유로 인해 엄청난 작업 공수가 들어갔고, 또 예외적인 상황도 제일 많이 발생했다. 특히 도메인 변경 같은 경우는 라이센스 라던지 생각치 못한 작업들이 존재했다.</p>
<p>과거의 잔재들을 물리치고 책임감을 갖고 하나씩 뚫고 가다 보니 전부 다 해결은 했지만 쉽지는 않았다.</p>
<h2 id="프로젝트-이행">프로젝트 이행</h2>
<p>모든 수정을 다 끝내고 프로젝트를 이행하는 날이 다가왔다. 놀랍게도 지금의 회사는 레거시가 굉장히 많다보니 SVN을 쓰고 있다. 일부 신규 프로젝트는 Git을 쓰기도 하지만 일부일뿐.</p>
<p>그러다 보니 Git의 소중함을 다시한번 깨달았다. 강력한 기능들을 갖고 있고, 조금만 익숙하면 소스를 잘 관리 할 수 있다. 이번에 SVN을 쓰면서 참 사람손이 많이 필요하다고 느꼈고, 워낙에 방대한 양의 소스가 수정되다 보니 내 시간을 참 많이 할애했다.</p>
<p>책임감을 가진 만큼의 결과가 나왔던 것 같다. 이행 후 크리티컬한 이슈는 없었고 몇가지 단순 오류건들을 해결하고 나니 성공적으로 끝낼 수 있었다.</p>
<h2 id="총평">총평</h2>
<p>사실 이번 프로젝트를 진행하면서 개발보다는 프로젝트 관리의 영역에 집중되었던 것 같다. 아직 개발적으로도 성장이 많이 필요하지만 전체적인 프로젝트의 흐름을 느낄 수 있었던 좋은 기회였다. </p>
<p>이번에는 개발자와 PL의 그 어디에선가 많은 고생을 한 것 같다. 하지만 이번에도 개발의 중요성을 굉장히 느꼈다. 개발을 모르는 상태에서는 어떤 프로젝트이든 성공시킬 수 없다. </p>
<p>그리고 정말 책임감이 많이 필요하다는 것도 느꼈다. 성공적인 완수를 위해서는 책임과 희생이 정말 필요하다.</p>
<blockquote>
<p>프로젝트를 한번 하고나니, 정말 진이 빠져서 몇 달간 벨로그에 소홀했던 것 같다. 다시 개발 열정을 불태워봐야겠다. 끝!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] 실행 컨텍스트, 호이스팅, this]]></title>
            <link>https://velog.io/@nowod_it/Javascript-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-this</link>
            <guid>https://velog.io/@nowod_it/Javascript-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-this</guid>
            <pubDate>Sun, 25 Sep 2022 11:03:08 GMT</pubDate>
            <description><![CDATA[<h1 id="javascript의-특징">Javascript의 특징</h1>
<p>자바스크립트의 특징에 대해서 검색을 해보면 이런 특징들이 보통 나열된다.</p>
<blockquote>
<ol>
<li>객체 기반의 스크립트 언어이다.</li>
<li>동적이며 타입을 명시하지 않는 인터프리터 언어이다.</li>
<li>객체지향 프로그래밍과 함수형 프로그래밍을 모두 표현할 수 있다.</li>
<li>웹 리소스 (HTML, CSS)등을 동적으로 다룰 수 있다.</li>
</ol>
</blockquote>
<p>여기서 가장 중요한 특징은 무엇일까? 
지금은 ES6, Node.js 등이 등장하면서 <strong>&quot;함수형 프로그래밍, 객체지향, 객체 기반&quot;</strong>등의 표현들이 중요해보인다. 
나도 사실 지금까지는 이런 것들이 자바스크립트의 중요한 특징이라고 생각했다.</p>
<p>그런데 사실 이러한 특징들을 본래 자바스크립트의 특징이라기 보다는 개발 패러다임에 변화에 따라서 생긴 특징이라고 볼 수 있다. </p>
<p>*<em>그렇다면 자바스크립트의 진정한 특징은 무엇일까?  *</em></p>
<h2 id="javascript의-역사">Javascript의 역사</h2>
<p>1995년 넷스케이프라는 회사에서 만든 웹브라우저가 있었고, 당시 넷스케이프에서 인터랙티브한 웹을 위해
&#39;MOCHA&#39;라는 언어를 만들었고, 이 언어가 최종적으로 &#39;Javascript&#39;라는 이름을 가지게 되었다.</p>
<p>Javascript는 웹 리소스들을 인터렉티브하게 표현하기 위해 등장한 언어다.
DOM을 다루고, 이벤트를 핸들링하기 위해 등장한 언어라는 것에 주목하고 다시 특징을 보자.</p>
<p>그러면 <strong>&quot;4. 웹 리소스 (HTML, CSS)등을 동적으로 다룰 수 있따.&quot;</strong> 이 문구가 눈에 띈다.
사실 이 특징이 Javascript의 기원인 것이다.</p>
<p>우리가 자바스크립트를 깊이 공부하다보면 마주하는 것들이 있다. 실행컨텍스트, 호이스팅, This의 개념이다.
이런 개념이 왜 자바스크립트에서 등장하는 것일까에 대한 해답이 바로 4번 특징이라고 생각한다.</p>
<blockquote>
<p><strong>&quot;4. 웹 리소스 (HTML, CSS)등을 동적으로 다룰 수 있다.&quot;</strong> </p>
</blockquote>
<p>정적인 리소스를 동적으로 다루기 위해서는 어떻게 해야할까? 
자바스크립트를 실행할 위치를 파악하고, 동적인 이벤트들을 정의해주어야 한다..
결과적으로 코드가 쓰여질 맥락(Context)을 파악해야 한다.</p>
<p>그래서 자바스크립트에는 실행 컨텍스트가 존재한다고 생각한다. <del>(뇌피셜이 들어가 있다..)</del></p>
<h1 id="실행-컨텍스트-execution-context">실행 컨텍스트 (Execution Context)</h1>
<blockquote>
<p>실행 컨텍스트는 자바스크립트 수행에 필요한 환경정보를 담은 객체이다.
이때 호이스팅이 발생되고, 환경정보를 구성하며, this 값이 설정된다.</p>
</blockquote>
<p>실행 컨텍스트는 2개로 나뉘어 진다.</p>
<ol>
<li>전역 컨테스트 (Global Execution Context)
 -&gt; 웹의 경우 window, Node.js의 경우 Global 객체를 this에 할당한다.</li>
<li>함수 컨텍스트 (Functional Execution Context)
 -&gt; 함수가 호출될 때 해당 함수에 대한 컨텍스트를 생성한다.</li>
</ol>
<p>실행 컨텍스트는 Last in, First Out의 구조로 실행된다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/67dff7fa-f7d9-4170-b440-1d34177fe932/image.png" alt=""></p>
<p>실행 컨텍스트라는 개념에 대해서 대부분 추상적인 개념이라고 생각한다.
하지만 자바스크립트가 탄생한 이유와 연관지어 생각해보면, 실행 컨텍스트는 맥락파악을 위해 필수적인 요소라는 것을 알 수 있다.</p>
<h1 id="호이스팅-hoisting">호이스팅 (Hoisting)</h1>
<p>실행 컨텍스트가 생성 될때 <em><strong>호이스팅이</strong></em> 발생되고, 환경정보를 구성하며, this 값이 설정된다.
호이스팅이 발생된다는데, 호이스팅은 무엇일까?</p>
<p>대부분 호이스팅을 설명할 때 아래와 같이 설명한다. </p>
<blockquote>
<p>호이스팅이란 자바스크립트에서 선언부를 상단으로 올리는 것이다.</p>
</blockquote>
<p>이 설명은 자바스크립트를 어느정도 해봤다면 다 경험해본 내용이다. 자바스크립트에서는 함수를 호출부 아래에 선언해도, 호출이 되는 경험은 누구나 해봤을 것이다.</p>
<p>그래서 왜 선언부로 올리는 것인가??</p>
<p><strong>바로 자바스크립트의 실행 컨텍스트 때문이다!!</strong></p>
<p>코드가 실행되어야할 문맥을 파악하기 위해서는 문맥 근처의 변수들을 알고 있어야한다.
함수가 호출될때 생기는 함수 컨텍스트에서는 함수가 호출되는 문맥을 파악해야 하고, 이를 위해서는 함수를 구성하고 있는 선언부들을 파악해야한다. <del>(사실 굉장히 추상적이고, 철학적인 부분이다..)</del>
이런 범위를 <strong>&quot;어휘적 환경(Lexical Environment)&quot;</strong>이라고 표현한다.</p>
<p>호이스팅의 대상이 되는 var, let, const, function, arrow function에는 몇가지 차이점이 있다.</p>
<p>간단하게 설명하자면 var, function은 함수레벨 스코프이며 undefined로 초기화 된다.
let, const, arrow function은 호이스팅의 대상이기는 하나 초기화 되지 않기에 상단에서 사용하려고 한다면 에러를 발생시킨다.</p>
<h2 id="var-let-const의-차이">var, let, const의 차이</h2>
<h3 id="--var">- var</h3>
<p>오랫동안 자바스크립트에서 사용되왔던 var는 변수를 정의할 때 사용하는 키워드이다.
var의 특징으로 인해 오랫동안 많은 개발자들이 고통받아 왔다.</p>
<ol>
<li>함수레벨의 스코프를 가진다.</li>
<li>var는 생략할 수 있다.</li>
<li>중복 선언이 된다.</li>
<li>호이스팅 시 undefined로 초기화된다.</li>
</ol>
<p>특징들을 보니.. 정말 제대로 다루기가 쉽지 않다. 자칫 잘못하면, 의도와 다른 동작이 이러나기가 굉장히 쉽다.</p>
<p>특히 호이스팅이 될 때 var는 선언과 동시에 undefined로 초기화 된다.
undefined에 대한 에러 처리를 하지 않으면, 어디에서 오류가 발생하는지 알 수 없는 상황이 생기기도 한다.</p>
<h3 id="--let">- let</h3>
<p>let은 ES6가 등장하면서 const와 함께 많이 쓰이고 있다. 
처음에 자바스크립트를 공부할 때는 단순히 &#39;var -&gt; let으로 변경하면 된다&#39;라고 생각한 적이 있었는데, 굉장히 아마추어같은 생각이었다. let은 var와 비슷하지만 다른 특징들이 있다.</p>
<ol>
<li>블록 수준 스코프(if, for와 같은 코드 블록)를 가진다.</li>
<li>생략이 불가능하며, 생략하는 경우 var로 할당된다.</li>
<li>중복으로 선언할 수 없다.</li>
<li>호이스팅의 대상이긴 하지만, 초기화 되지는 않는다.</li>
</ol>
<p>var와 비교하면 굉장히 안전해진? 특징들이라고 볼 수 있다.
그러나 사실 자바스크립트에서는 필요한 경우가 아니면 let을 쓰지 않는다.</p>
<h3 id="--const">- const</h3>
<p>const 역시 ES6가 등장하면서 굉장히 많이 쓰이고 있다.
const는 할당된 값을 변경할 수 없다.
하지만 주소값이 할당되는 경우(배열, 객체 등)에는 배열 값을 추가하거나 하는 행위가 주소를 변경하지 않기에, 값 변경이 가능하다.
const가 많이 쓰이는 이유에는 좋은 특징들이 있다.</p>
<ol>
<li>블록 수준 스코프(if, for와 같은 코드 블록)를 가진다.</li>
<li>생략이 불가능하며, 생략하는 경우 var로 할당된다.</li>
<li>중복으로 선언할 수 없다</li>
<li>호이스팅의 대상이긴 하지만, 초기화 되지는 않는다.</li>
<li>변경되지 않을 값이기에 안정적인 개발이 가능하다.</li>
<li>let, var와 같이 재할당이 안되기에 성능 최적화에 도움이 된다.</li>
</ol>
<h1 id="this">this</h1>
<p>실행컨텍스트가 생성될 때 this의 값이 할당된다. 
사실 실행 컨텍스트를 알기전에 나에게 this는.. <strong>&quot;함수호출 전에는 window 객체, 함수가 호출될 때는 함수를 호출한 객체 정도로 이해하고 있었다.&quot;</strong>
하지만 그냥 그렇게 이해할 뿐이지 this가 왜 생겼는지는 알지 못했다.</p>
<p>하지만 실행 컨텍스트의 생성이유와 역사를 이해하면서 this라는 것을 조금이나마 이해할 수 있었다.</p>
<blockquote>
<p>this : 정의된 함수가 어떻게 발생되었는지에 따라 해당 대상의 컨텍스트를 가르키는 객체.</p>
</blockquote>
<p>이게 무슨 말일까?</p>
<p>몇가지 예시를 보자. 옆에 달아 놓은 주석을 보면서 this를 이해해보자</p>
<p>[첫번째 예시]</p>
<pre><code class="language-javascript">var someValue = &#39;hello&#39;;
function outerFunc() {
    console.log(this.someValue); // 첫번째 : World (this: obj), 두번째 : hello (this: window)
    this.innerFunc();
}
const obj = {
    someValue : &#39;world&#39;,
    outerFunc,
    innerFunc : function() {
        console.log(&quot;innerFunc&#39;s this : &quot;, this); // 첫번째 : this: obj, 두번째 : window 객체는 innerFunc가 없기에 에러 발생
    }
}
obj.outerFunc(); // 첫번째 (obj 객체가 OuterFunc를 invoke 하였기에 this는 obj가 된다.)
outerFunc(); // 두번째 (window 객체가 OuterFunc를 invoke 하였기에 this는 window가 된다.)</code></pre>
<p>[두번째 예시]</p>
<pre><code class="language-javascript">var value = 1;

var obj = {
  value: 100,
  foo: function() {
    setTimeout(function() {
      console.log(&quot;callback&#39;s this: &quot;,  this);  // setTimeout의 this는 window이다.
      console.log(&quot;callback&#39;s this.value: &quot;,  this.value);  // window.value = 1
      function bar() {
        console.log(&quot;bar&#39;s this: &quot;, this);  // setTimeout 안에 있으니, window 입니다.
      }
      bar();
    }, 1000);
  }
};

obj.foo();</code></pre>
<p>이렇듯 실행되는 문맥에 따라 this값이 할당되게 됩니다.
단순히 외워야 되는 것이 아니라, 함수가 실행되는 원리를 따라가다 보면 자연스럽게 this 값을 이해할 수 있습니다.</p>
<h1 id="후기">후기</h1>
<p>자바스크립트의 특징이라고 볼 수 있는 실행컨텍스트, 호이스팅, this에 대해서 알아보았는데,
참 많은 생각을 하게 되는 내용이었다.</p>
<p>자바스크립트가 생기게 된 역사부터 차근차근히 따라오다보니 어느정도 이해도 되지만, 깊이 갈수록 철학적인 느낌이 굉장히 많이 든다.</p>
<p>사실 나는 문과 출신의 개발자다. 개발이 너무 재미있지만 항상 문과적인 내용과는 동떨어져 있다고 생각했는데, 이런 언어의 특징들이 문과적인 요소에서 왔다는 생각을 하니 너무 재미있었다.</p>
<p>이 글을 읽는 모든 분들도 재미있었으면 좋겠다..</p>
<h3 id="출처">출처</h3>
<blockquote>
<p><a href="https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42">자바스크립트는 왜 프로토타입을 선택했을까?</a>
<a href="https://catsbi.oopy.io/fffa6930-ca30-4f7e-88b6-28011fde5867">실행컨텍스트와 자바스크립트의 동작 원리</a>
<a href="https://yceffort.kr/2020/05/var-let-const-hoisting">var let const,그리고 호이스팅</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Redux Toolkit 과 RTK Query 정리]]></title>
            <link>https://velog.io/@nowod_it/React-Redux-Toolkit-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@nowod_it/React-Redux-Toolkit-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 06 Sep 2022 14:17:00 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/nowod_it/post/3d9556d5-b41b-429f-9552-62929ef9a8ab/image.png" alt=""></p>
<blockquote>
<p>출처 : <a href="https://redux-toolkit.js.org/introduction/getting-started">https://redux-toolkit.js.org/introduction/getting-started</a></p>
</blockquote>
<h1 id="redux-toolkit">Redux Toolkit?</h1>
<p>리덕스 팀에서 만든 공식적인 Redux Tool이다. 홈페이지를 가보면 아래와 같이 설명이 되어있는데 자신감을 엿볼 수 있다.</p>
<blockquote>
<p>The official, opinionated, batteries-included toolset for efficient Redux development
&#39;Redux 개발을 위한 공식적인, 독단적인, 배터리포함 ToolSet&#39;</p>
</blockquote>
<h3 id="1-공식적인">1. 공식적인</h3>
<ul>
<li>Redux 팀에서 만든 공식적인 라이브러리.</li>
</ul>
<h3 id="2-독단적인">2. 독단적인</h3>
<ul>
<li>스토어 설정을 위한 default를 제공하며 가장 일반적으로 사용되는 Redux 기능들을 포함. (Redux의 Default를 제공하는 자신감!!?)</li>
</ul>
<h3 id="3-배터리포함-관용구적-표현임">3. 배터리포함!! (관용구적 표현임)</h3>
<ul>
<li>Redux Toolkit만 사용해도 아무 문제 없다!</li>
</ul>
<h1 id="why-redux-toolkit">Why Redux Toolkit?</h1>
<h3 id="기존-redux의-문제점">기존 Redux의 문제점</h3>
<ol>
<li>&quot;Redux Store 설정이 너무 복잡하다&quot;</li>
<li>&quot;Redux를 효율적으로 쓰려면 많은 패키지가 필요하다.&quot;</li>
<li>&quot;Redux에는 너무 많은 boilerplate code가 있다.&quot;</li>
</ol>
<p>-&gt; Redux Toolkit을 사용하면 이러한 문제점을 해결하고, 유지보수가 쉽다!</p>
<h1 id="redux-toolkit-제공-기능">Redux Toolkit 제공 기능</h1>
<h3 id="기본기능">기본기능</h3>
<p><strong>configureStore():</strong> createStore를 랩핑하여 간단하게 store를 만들어줍니다.</p>
<p><strong>createReducer():</strong> 복잡하게 switch 문을 작성하는 대신 간단하게 reducer를 만들어줍니다.</p>
<p><strong>createAction():</strong> 간단하게 액션들을 만들어줍니다.</p>
<p><strong>createSlice():</strong> 리듀서 함수의 객체, 슬라이스 이름, 초기 상태 값을 받아 해당 액션 생성자와 액션 유형을 가진 슬라이스 리듀서를 자동으로 생성합니다.</p>
<p><strong>createAsyncThunk:</strong> 프로미스 기반의 액션들을 dispatch 하는 thunk를 생성합니다.</p>
<p><strong>createEntityAdapter:</strong> 스토어에서 정규화된 데이터를 관리하기 위해 재사용 가능한 리듀서 및 셀렉터 세트를 생성합니다.</p>
<h3 id="rtk-query-기능">RTK Query 기능</h3>
<blockquote>
<p>출처 : <a href="https://junsangyu.gitbook.io/rtk-query/overview">https://junsangyu.gitbook.io/rtk-query/overview</a></p>
</blockquote>
<p>RTK Query는 강력한 data fetching, caching 툴입니다. 웹 애플리케이션에서 데이터를 가져오는 상황을 간단하게 만들어서 data fetching과 caching 로직을 스스로 작성할 필요가 없도록 만들어졌습니다.</p>
<ul>
<li><p>데이터 패칭과 캐싱 로직은 Redux Toolkit의 createSlice와 createAsyncThunk API 위에서 동작합니다.</p>
</li>
<li><p>Redux Toolkit은 UI 독립적이기 때문에 RTK Query의 기능들은 모든 UI 계층에서 사용할 수 있습니다.</p>
</li>
<li><p>API 엔드포인트는 인자로부터 쿼리 파리미터를 생성하고 캐싱을 위해 응답을 변환하는 방법을 포함해서 미리 정의됩니다.</p>
</li>
<li><p>RTK Query는 데이터 패칭 프로세스를 캡슐화해서 data와 isLoading필드를 컴포넌트에게 제공하고, 컴포넌트가 mount, unmount시 캐시 된 데이터의 라이프타임을 관리하는 React hook을 제공합니다.</p>
</li>
</ul>
<h1 id="redux와-redux-toolkit-비교">Redux와 Redux Toolkit 비교</h1>
<h3 id="reducer-생성">Reducer 생성</h3>
<p>Redux : switch 기반으로 Action을 분기하여 Reducer를 만든다.</p>
<pre><code class="language-javascript">function todosReducer(state = [], action) {
  switch (action.type) {
    case &#39;ADD_TODO&#39;: {
      return state.concat(action.payload)
    }
    case &#39;TOGGLE_TODO&#39;: {
      const { index } = action.payload
      return state.map((todo, i) =&gt; {
        if (i !== index) return todo

        return {
          ...todo,
          completed: !todo.completed,
        }
      })
    }
    case &#39;REMOVE_TODO&#39;: {
      return state.filter((todo, i) =&gt; i !== action.payload.index)
    }
    default:
      return state
  }
}</code></pre>
<p>Redux Toolkit : builder를 통해 좀더 간단하게 액션을 생성한다.</p>
<pre><code class="language-javascript">const todosReducer = createReducer([], (builder) =&gt; {
  builder
    .addCase(&#39;ADD_TODO&#39;, (state, action) =&gt; {
      // &quot;mutate&quot; the array by calling push()
      state.push(action.payload)
    })
    .addCase(&#39;TOGGLE_TODO&#39;, (state, action) =&gt; {
      const todo = state[action.payload.index]
      // &quot;mutate&quot; the object by overwriting a field
      todo.completed = !todo.completed
    })
    .addCase(&#39;REMOVE_TODO&#39;, (state, action) =&gt; {
      // Can still return an immutably-updated value if we want to
      return state.filter((todo, i) =&gt; i !== action.payload.index)
    })
})</code></pre>
<h3 id="action-생성">Action 생성</h3>
<p>Redux : 하나의 function에서 Action Type과 Payload가 정의된다.</p>
<pre><code class="language-javascript">function addTodo(text) {
  return {
    type: &#39;ADD_TODO&#39;,
    payload: { text },
  }
}</code></pre>
<p>Redux Toolkit : createAction으로 액션을 만들고 따로 매개변수를 받는다.</p>
<pre><code class="language-javascript">const addTodo = createAction(&#39;ADD_TODO&#39;)
addTodo({ text: &#39;Buy milk&#39; })

/* Reducer와 조합 */
const actionCreator = createAction(&#39;SOME_ACTION_TYPE&#39;)

const reducer = createReducer({}, (builder) =&gt; {  
  builder.addCase(actionCreator, (state, action) =&gt; {})
})</code></pre>
<h3 id="createslice">createSlice</h3>
<p>Redux : Reducer를 만들기 위해 액션 정의, 액션 함수 정의, switch를 통해 액션타입 별 코드 정의를 진행한다.</p>
<pre><code class="language-javascript">// postsConstants.js
const CREATE_POST = &#39;CREATE_POST&#39;
const UPDATE_POST = &#39;UPDATE_POST&#39;
const DELETE_POST = &#39;DELETE_POST&#39;

// postsActions.js
import { CREATE_POST, UPDATE_POST, DELETE_POST } from &#39;./postConstants&#39;

export function addPost(id, title) {
  return {
    type: CREATE_POST,
    payload: { id, title },
  }
}

// postsReducer.js
import { CREATE_POST, UPDATE_POST, DELETE_POST } from &#39;./postConstants&#39;

const initialState = []

export default function postsReducer(state = initialState, action) {
  switch (action.type) {
    case CREATE_POST: {
      // omit implementation
    }
    default:
      return state
  }
}</code></pre>
<p>Redux Toolkit : createSlice를 통해 간단하게 reducers안에 액션을 생성하고 정의한다. 중복되는 변수 사용이 줄어든다.</p>
<pre><code class="language-javascript">const postsSlice = createSlice({
  name: &#39;posts&#39;,
  initialState: [],
  reducers: {
    createPost(state, action) {},
    updatePost(state, action) {},
    deletePost(state, action) {},
  },
})

console.log(postsSlice)
/*
{
    name: &#39;posts&#39;,
    actions : {
        createPost,
        updatePost,
        deletePost,
    },
    reducer
}
*/

const { createPost } = postsSlice.actions

console.log(createPost({ id: 123, title: &#39;Hello World&#39; }))
// {type : &quot;posts/createPost&quot;, payload : {id : 123, title : &quot;Hello World&quot;}}
})</code></pre>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/66a27a7b-bc91-4e5b-bef5-3f1acee9563f/image.png" alt=""></p>
<h1 id="rtk-query">RTK Query</h1>
<h4 id="위의-redux-toolkit의-간단하고-파워풀한-reducer-생성을-토대로-거기에-데이터를-효과적으로-가져오도록-한다">위의 Redux Toolkit의 간단하고 파워풀한 Reducer 생성을 토대로, 거기에 데이터를 효과적으로 가져오도록 한다.</h4>
<h3 id="reducer-생성-1">reducer 생성</h3>
<ol>
<li>createApi를 이용하여 reducer를 만듭니다.
 -&gt; reducerPath는 이 reducer의 이름입니다. configureStore에서 사용합니다.
 -&gt; endpoints는 API 수행 단위입니다. BaseUrl 기반 API를 분리하는 목적입니다.</li>
<li>reducer가 생성되면 자동으로 &quot;use + endpoints 단위 + Query&quot; 훅이 생성됩니다.</li>
<li>configureStore에 reducer를 정의합니다.</li>
<li>앱 최상단에 Provider를 생성하여 Store를 추가합니다.</li>
<li>필요한 컴포넌트에서 자동생성된 훅을 사용하여 data를 가져옵니다.</li>
</ol>
<pre><code class="language-javascript">import { createApi, fetchBaseQuery } from &#39;@reduxjs/toolkit/query/react&#39;
//createApi(): RTK Query 기능의 코어입니다. 데이터를 패치하고 변환하는 설정을 포함해서 엔드포인트들에서 어떻게 데이터를 패치하는지 정의할 수 있습니다.

//fetchBaseQuery(): 간단한 요청을 위한 fetch의 래퍼입니다. 대부분의 사용자에게 createApi의 baseQuery로 권장합니다.

const baseUrl = &#39;api_test.com&#39;

// 이렇게 createRequest를 만들어서 사용하지 말것!
// 이미 baseQuery는 아래와 같은 형태를 지원하기에 이런식으로 만들필요가 없다.
//개발자 권고에 따라 삭제 : const createRequest = (url) =&gt; ({url})

export const someApi = createApi({
    reducerPath: &#39;someApi&#39;,
    baseQuery: fetchBaseQuery({ 
      baseUrl,
//    either you can just set `headers` here:
//    headers: { &quot;Accept&quot;: &quot;application/vnd.api+json&quot; }

//    or you use `prepareHeaders` where you can do some calulations and have access to stuff like `getState` or the endpoint name
      prepareHeaders: (headers, { getState, endpoint, type, forced }) =&gt; {
         headers.set(&quot;Accept&quot;, &quot;application/vnd.api+json&quot;)
         return headers
      }}),
    endpoints: (builder) =&gt; ({
        getSome: builder.query({
            query: (count) =&gt; {url : `/some?limit=${count}`}
        }),
        getSomes: builder.query({
            query: ({ coinUuid, timePeriod }) =&gt; {url :`/somes`}
        }),       
    })
})

export const { useGetSomeQuery, useGetSomesQuery } = someApi;</code></pre>
<pre><code class="language-javascript">//store 생성
export default configureStore({
    reducer: {
        [someApi.reducerPath]: someApi.reducer,
    },
});</code></pre>
<pre><code class="language-javascript">//root에 정의
const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;React.StrictMode&gt;
    &lt;BrowserRouter&gt;
      &lt;Provider store={store}&gt;
        &lt;App /&gt;
      &lt;/Provider&gt;
    &lt;/BrowserRouter&gt;
  &lt;/React.StrictMode&gt;
);</code></pre>
<pre><code class="language-javascript">// 컴포넌트에서 활용
const someComponents = ({count}) =&gt; {
  const { data, isFetching } = useGetSomeQuery({count});

  if(isFetching) return &#39;Loading...&#39;

  return (
      &lt;&gt;
        &lt;p&gt;Good!&lt;/p&gt;
    &lt;/&gt;
  )

}</code></pre>
<h3 id="rtk-query-사용-후기">RTK Query 사용 후기</h3>
<blockquote>
<p>개인적으로 느낀 가장 큰 장점은 <strong>state 변경에 따라 자동으로 다시 데이터를 불러오는 점이다!</strong></p>
</blockquote>
<p>아래와 같이 컴포넌트에서 state의 값을 RTK-Query의 훅에 전달하는 경우가 있는데, 이때 RTK-Query에서 해당 state의 변경을 자동으로 읽어서 다시 데이터를 가져온다. 굳이 useEffect를 사용하지 않아도, 필요한 부분을 다시 랜더링해오는 기능은 정말 편하고 유용했다.</p>
<p>물론 데이터 캐싱도 자동으로 되는 것도 정말 좋았지만, 여러모로 사용자 관점에서 편하고 효율적으로 사용할 수 있다는 생각이 들었다.</p>
<pre><code class="language-javascript">// 컴포넌트에서 활용
const someComponents = ({count}) =&gt; {
  const [count, setcount] = useState(0);
  const { data, isFetching } = useGetSomeQuery({count});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] debounce와 throttle]]></title>
            <link>https://velog.io/@nowod_it/Javascript-debounce%EC%99%80-throttle</link>
            <guid>https://velog.io/@nowod_it/Javascript-debounce%EC%99%80-throttle</guid>
            <pubDate>Wed, 31 Aug 2022 06:41:09 GMT</pubDate>
            <description><![CDATA[<h2 id="비효율적인-이벤트-처리">비효율적인 이벤트 처리?</h2>
<p>Input 이벤트, 스크롤 이벤트, 마우스 이벤트 등 빈번하게 발생할 때, API 호출과 같이 비용이 많이 드는 처리가 발생하는 경우가 있다.</p>
<p>검색 기능을 개발하거나, 스크롤에 따라 데이터가 불러지거나 하는 경우에 이벤트가 발생할 때마다 많은 트래픽이 발생하면서 비효율은 물론 성능에 까지 악영향을 끼칠 수 있다.</p>
<p>이런 경우를 핸들링 할 수 있는 몇가지 방법이 있다. 캐시를 만든다거나, debounce와 throttle을 적용하는 방법 등이다.</p>
<h2 id="debounce와-throttle">debounce와 throttle?</h2>
<ul>
<li>debounce는 입력의 종료지점을 파악해서 최종적으로 한번만 이벤트가 실행될 수 있도록 하는 기법이다. </li>
<li>throttle은 이벤트를 일정한 주기로 실행시키는 기법이다.</li>
</ul>
<p>: 두 방법 모두 과도한 이벤트 발생을 막고, 제약을 통해 성능향상과 더불어 사용자 경험 향상에 도움을 주는 방법이다.</p>
<h2 id="debounce-예제">debounce 예제</h2>
<pre><code class="language-javascript">const debounce = (callback, limit = 250) =&gt; {
  let timeout

  return function (...args) {
    clearTimeout(timeout)
    timeout = setTimeout(() =&gt; {
        callback.apply(this, args)
    }, limit)
  }
}

const debounceEvent = debounce((event) =&gt; {
    const data = event.target.value.trim();
    state.searchText = data;
    onChangeHandler(state.searchText);
});

searchInput.addEventListener(&quot;input&quot;, debounceEvent);</code></pre>
<p>: debounce에 대한 구현은 클로져함수와 timeout을 활용하여 만들 수 있다.</p>
<ol>
<li>debounce를 이용한 debounceEvent 함수를 만든다.</li>
<li>필요한 event에 debounceEvent를 붙인다.</li>
</ol>
<p>위 코드는 input에 텍스트 입력을 감지한 후 250ms 동안 입력이 없으면 정의된 함수를 실행한다.</p>
<p>-&gt; 사용자의 입력이 완료될 때까지 불필요한 API 호출을 막을 수 있다.</p>
<h2 id="throttle-예제">throttle 예제</h2>
<pre><code class="language-javascript">const throttle = (callback, wait = 500) =&gt; {
  let waiting = true;  

  return function(...args) {
    if (waiting) {
      callback.apply(this, args);       
      waiting = false;  

      setTimeout(() =&gt; {
        waiting = true;
      }, wait);
    }
  };  
};</code></pre>
<p>: throttle에 대한 구현도 debounce와 마찬가지로 클로져함수와 timeout을 활용하여 만들 수 있다.</p>
<ol>
<li>함수 실행을 위한 waiting 변수를 만든다.</li>
<li>실행 후 waiting 변수를 false로 만든다.</li>
<li>일정 시간 후 waiting 변수를 true로 만든다.</li>
<li>함수가 실행된다.</li>
</ol>
<p>만약 스크롤 이벤트 처럼 지속적으로 발생해서 끝이 잘 나지 않은 이벤트가 발생했을 때 debounce를 적용한다면, 종료 지점을 알수가 없어 함수 실행이 어렵다.</p>
<p>하지만 throttle을 사용한다면 이벤트의 종료와 상관없이, 일정시간마다 함수를 호출하기에 효율적으로 함수를 실행할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Next.js 사용 후기]]></title>
            <link>https://velog.io/@nowod_it/React-Next.js-%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@nowod_it/React-Next.js-%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 23 Aug 2022 15:17:08 GMT</pubDate>
            <description><![CDATA[<h2 id="nextjs">Next.js?</h2>
<p>Next.js는 React 기반의 SSR 프레임워크입니다. Vercel에서 오픈소스로 배포, 관리하는 프레임워크로서 많은 기업에서 사용되고 있습니다.</p>
<h2 id="nextjs를-쓰는-이유">Next.js를 쓰는 이유?</h2>
<ol>
<li>SSR(Server Side Rendering)이 손쉽게 가능하다.</li>
</ol>
<ul>
<li>SSR은 서버에서 즉시 렌더링할 수 있는 HTML 파일을 만들고 유저는 접속 즉시 렌더링된 화면을 볼 수 있다.</li>
<li>CSR의 경우 한번에 HTML, CSS, JS의 모든 자원을 불러오지만, SSR의 경우 필요한 부분만 불러와 속도가 더 빠르다.
<img src="https://velog.velcdn.com/images/nowod_it/post/10e6b8fd-5f0a-4d7d-bd2b-be65bc2aeb6b/image.png" alt=""></li>
</ul>
<ol start="2">
<li>간편한 라우팅 기능.</li>
</ol>
<ul>
<li>Next.js를 설치하여 사용하게 되면 pages라는 폴더가 생긴다. 해당 폴더 안에 필요한 js파일을 생성하면, 자동으로 해당 페이지가 만들어진다.</li>
<li>또한 404, 403 등의 에러페이지도 404.js, 403.js만 만들어도 에러페이지 대응이 가능하다.
<img src="https://velog.velcdn.com/images/nowod_it/post/faf63b53-5b97-47e1-800e-afdc7d96cc93/image.png" alt=""></li>
</ul>
<ol start="3">
<li>이미지 최적화 기능.</li>
</ol>
<ul>
<li>이미지 태그를 통해서 WebP와 같은 최신 이미지 형식으로 이미지를 자동으로 제공한다. 또한 캐싱 및 레이징 로딩과 같은 기능을 통해 더 빠른 속도로 로딩이 가능하다.<pre><code class="language-javascript">import Image from &quot;next/image&quot;;
</code></pre>
</li>
</ul>
<p><Image src={coverImage}
       width="100%"
       height="30%"
       layout="responsive"
       objectFit="fill"
       quality={100}
       alt=""
/></p>
<pre><code>
4. 데이터 가져오는 기능 제공

- getStaticProps()를 사용하면 몇가지 설정만으로 데이터 결과값을 prop으로 바로 넘길 수 있다.

```javascript
export default function Projects({ data }) {
    return (
        &lt;&gt;
              &lt;h1&gt;{data}&lt;/h1&gt;
          &lt;/&gt;
    )
}

// 데이터 fetch 결과를 props에 저장하면 바로 렌더링 함수에서 사용가능.
export async function getStaticProps(context) {
  const res = await fetch(&quot;~~~&quot;);
  const data = await res.json();

  return {
    props: {
      data
    }, // will be passed to the page component as props
  }
}</code></pre><ol start="5">
<li>그외 다양한 장점들 (built in css, Hot Module Replacement, 코드 분할 등등)</li>
</ol>
<h2 id="결론">결론</h2>
<p>사실 처음에는 토스 프론트엔드 팀에서 사용한다고해서 관심이 있었는데, 직접 써보고 나니 많은 매력이 느껴진다. 사실 React에 익숙한 사람이라면 무리 없이 쓸 수 있을 정도이지만, 자체적으로 제공하는 기능도 매력적이고 SSR에 대해서 경험해보고 싶다면 굉장히 좋은 툴이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] 웹 브라우저 작동 원리 이해]]></title>
            <link>https://velog.io/@nowod_it/Javascript-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@nowod_it/Javascript-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Sun, 21 Aug 2022 05:27:48 GMT</pubDate>
            <description><![CDATA[<h2 id="자바스크립트의-기원">자바스크립트의 기원?</h2>
<p>자바스크립트는 원래 넷스케이프(예전에 사용되던 웹브라우저)에서 사용되는 언어였다. 점차 언어가 발전되면서 여러 형태로 변형되면서 Mozila Firefox, Internet Explorer, Chrome등에 사용되었다. 그리고 점차 표준화를 거치면서 웹과 Node.js를 통해 서버에도 사용되는 언어가 되었다.</p>
<p>애초에 웹브라우저에서 동작하는게 기본으로 고안된 언어이다 보니, 웹브라우저에서 Javascript를 해석하는 방식을 이해가 필요하다.</p>
<h2 id="웹브라우저">웹브라우저</h2>
<p>웹 브라우저가 화면을 띄우기 위해서는 렌더링 엔진과 네트워크, 자바스크립트 엔진이 주요한 기능을 한다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/2ba7c55b-02ec-4e7c-b948-cb50bf3b2a66/image.png" alt=""></p>
<p>렌더링 엔진은 기본적인 html, css의 처리를 하고, 네트워크는 통신에 대한 처리를 한다.
그리고 자바스크립트 엔진은 Javascript를 해석하고 작동시킨다.</p>
<h2 id="javascript-engine의-이해">Javascript Engine의 이해</h2>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/d9ff6b41-b0c9-48fa-8d4a-46c53cf8fb5c/image.png" alt=""></p>
<p>웹브라우저는 스택과 큐를 이용해서 자바스크립트를 실행한다.
스택은 기본적인 Javascript 코드를 이행시킨다.
큐에는 API 통신이나, 이벤트 처럼 실행시간이 오래걸리는 것들을 모아놓고,
스택이 비워지면 스택으로 내보내서 코드를 이행시킨다.</p>
<p>순차적으로 코드가 실행되는 다른 언어들과는 달리, Javascript는 이러한 특성들 때문에 내가 생각한 것과 다르게 코드가 동작 될 수 있다.</p>
<p>대표적인 예가 바로 아래 코드이다.</p>
<pre><code class="language-javascript">// 2,4,6이 순차적으로 출력된다.
console.log(1+1);
console.log(2+2);
console.log(3+3);

// 2,4,6이 바로 실행된다. (setTimeout은 큐로 쌓이기 때문에 Stack에 쌓인 console.log 3개가 전부 실행되고 실행된다.)
console.log(1+1);
//1초 쉬는 코드
setTimeout(() =&gt; {}, 1000);
console.log(2+2);
console.log(3+3);

//이렇게 해야 2 출력 후 1초 쉬고 4,6이 출려된다.
console.log(1+1);
//1초 쉬는 코드
setTimeout(() =&gt; {
    console.log(2+2);
    console.log(3+3);
}, 1000);
</code></pre>
<p>큐에는 WebAPIs, DOM Events, fetch, setTimeout, setInterval이러한 코드들이 쌓이게 된다.
그리고 쌓여있는 코드는 스택이 비어있을 때 실행이 되는데 만약 하나의 이벤트 처리가 끝나지 않으면 다른 이벤트들을 아무리 실행하려고 해도 실행되지 않는다.</p>
<p>브라우저가 응답없음을 내뱉는 경우들이 바로 이런 경우들일 것이다.</p>
<p>결론적으로 웹브라우저에 대한 이해를 하고 있으면, 이벤트 처리에 대해서 여러 해결방법을 생각할 수 있다. 네트워크 통신에 타임아웃을 걸거나, 이벤트에 너무 많은 처리를 넣지 않거나 항상 이런 동작을 생각하면서 개발을 하는 것이 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CSS] fixed, sticky 헤더 사용 시 anchor 위치 문제 해결방법 -> scroll-margin]]></title>
            <link>https://velog.io/@nowod_it/CSS-fixed-sticky-%ED%97%A4%EB%8D%94-%EC%82%AC%EC%9A%A9-%EC%8B%9C-anchor-%EC%9C%84%EC%B9%98-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95-scroll-margin</link>
            <guid>https://velog.io/@nowod_it/CSS-fixed-sticky-%ED%97%A4%EB%8D%94-%EC%82%AC%EC%9A%A9-%EC%8B%9C-anchor-%EC%9C%84%EC%B9%98-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95-scroll-margin</guid>
            <pubDate>Wed, 17 Aug 2022 15:12:18 GMT</pubDate>
            <description><![CDATA[<h2 id="fixed-sticky">Fixed? Sticky?</h2>
<p>보통 화면 상단에 메뉴를 고정하거나 할 때 fixed position을 자주 사용합니다.
fixed를 사용하면 화면 상단에 메뉴가 고정되게 만들 수 있습니다.
<img src="https://velog.velcdn.com/images/nowod_it/post/14882534-7349-4a47-8493-7e79ce69ed90/image.png" alt=""></p>
<p>최근에는 비슷한 역할이지만 좀 더 부드러운 동작을 지원하는 sticky position이 생겼는데요.
sticky를 사용하면 static하게 동작하다가 임계점 이후에는 fixed하게 동작합니다.</p>
<h2 id="고정된-메뉴에서-발생하는-문제점">고정된 메뉴에서 발생하는 문제점</h2>
<p>일반적인 스크롤 상황에서는 사실 자연스럽게 유저들이 화면을 넘기기 때문에 큰 문제는 발생하지 않습니다.
그런데 만약 페이지내에 anchor를 활용해서 접근하는 경우에는 고정된 메뉴로 인해 의도하지 않게 화면이 보일 수 있습니다.</p>
<blockquote>
<p>원페이지로 이루어지는 웹사이트의 경우 한번에 보여주는 내용이 많아 스크롤이 길어지는 경우가 있습니다. 이런경우 anchor 태그를 이용해 이동할 수 있습니다.</p>
</blockquote>
<pre><code class="language-html">&lt;section id=&#39;about&#39;&gt;&lt;/section&gt;
&lt;a href=&#39;#about&#39;&gt;about&lt;/a&gt;</code></pre>
<ol>
<li><p>원하는 화면
<img src="https://velog.velcdn.com/images/nowod_it/post/6de28013-0adb-4a2d-9f51-d3b08d2c20e3/image.png" alt=""></p>
</li>
<li><p>의도치 않은 화면 (상단 메뉴로 인해 제목이 가려짐)
<img src="https://velog.velcdn.com/images/nowod_it/post/5af18199-2dde-4940-8285-68c670d0458c/image.png" alt=""></p>
</li>
</ol>
<h2 id="해결방법-몇-가지">해결방법 몇 가지</h2>
<ol>
<li>CSS 활용
span태그를 활용하여 목적지 div 상단에 배치 후 영역을 차지하게 만듭니다.
visiblity: hidden; 을 사용하게 되면 화면은 안보이지만 영역은 차지하는 속성을 이용합니다.</li>
</ol>
<pre><code class="language-html">&lt;span class=&quot;anchor&quot; id=&quot;about&quot;&gt;&lt;/span&gt;
&lt;div&gt;&lt;/div&gt;

&lt;style&gt;
.anchor{
    display: block;
    height: 80px; /*고정메뉴 높이*/
    margin-top: -80px; /*고정메뉴 높이*/
    visibility: hidden;
}
&lt;/style&gt;</code></pre>
<ol start="2">
<li>jquery 활용
타겟의 offset을 잡아 스크롤 시 고정메뉴 만큼 빼고 스크롤을 하는 방법이다.
개인적으로 jquery를 좋아하지 않기 때문에 사용하지 않았다.</li>
</ol>
<pre><code class="language-javascript">var offset = $(&#39;:target&#39;).offset();
var scrollto = offset.top - 60; // minus fixed header height
$(&#39;html, body&#39;).animate({scrollTop:scrollto}, 0);</code></pre>
<ol start="3">
<li><strong>scroll-margin 활용</strong>
scroll-margin 속성은 비교적 최근에 나온 것으로, 페이지간 스크롤 시에 margin을 적용한다.
특히 anchor나 scroll-snap-type을 통해서 스크롤의 결과로 하나의 페이지를 보여주는 경우, 상단에 고정된 메뉴를 원하는 만큼 피할 수 있다.
가장 쉽게 사용할 수 있으나, IE같은 구버전 브라우저는 사용하기 어려울 수 있다.<pre><code class="language-css">.div_section {
margin-top: 5em;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;  //하나의 div를 페이지형태로 보여줌(Viewport Height의 100%)
scroll-margin-top: 80px; // 고정 메뉴 높이만큼 설정
}</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Redux 정리]]></title>
            <link>https://velog.io/@nowod_it/React-Redux-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@nowod_it/React-Redux-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 07 Aug 2022 05:33:24 GMT</pubDate>
            <description><![CDATA[<h2 id="redux란-무엇인가">Redux란 무엇인가?</h2>
<p>리덕스(Redux)란 리액트에서 가장 사용률이 높은 상태관리 라이브러리입니다.
리액트 컴포넌트들의 상태 로직들이나 글로벌 상태 관리를 손쉽게 해줄 수 있습니다.</p>
<p>리덕스는 Action, Reducer, Store, View로 구성된 하나의 패턴이다.</p>
<p><img src="https://velog.velcdn.com/images/nowod_it/post/5fd8ca33-c4bb-45c6-a77b-ba088f185874/image.png" alt=""></p>
<ol>
<li>UI에서 클릭 이벤트가 발생한다.</li>
<li>이벤트 핸들러에서 해당되는 Action을 Dispatch한다.</li>
<li>Action이 Reducer로 넘어오면서 state값을 변경한다.</li>
<li>변경된 state 값이 View(UI)로 전달된다.</li>
</ol>
<p>이러한 일련의 과정을 통해 상태를 다루게 된다.</p>
<h2 id="redux-관련-키워드">Redux 관련 키워드</h2>
<blockquote>
<p>인용 : <a href="https://react.vlpt.us/redux/01-keywords.html">https://react.vlpt.us/redux/01-keywords.html</a> </p>
</blockquote>
<h3 id="1-action">1. Action</h3>
<ul>
<li>상태에 변화가 필요할 때 액션을 발생시키게 되는데 이때 액션은 하나의 객체로 전달된다.<pre><code class="language-javascript">{
type: &quot;ADD_HELLO&quot;,
text: &quot;Hello World&quot;
} </code></pre>
</li>
</ul>
<h3 id="2-action-생성함수">2. Action 생성함수</h3>
<ul>
<li>액션 생성함수는, 액션을 만드는 함수입니다.<pre><code class="language-javascript">export const addHello = text =&gt; ({
type: &quot;ADD_HELLO&quot;,
text
})</code></pre>
</li>
</ul>
<h3 id="3-reducer">3. Reducer</h3>
<ul>
<li>리듀서는 상태를 변화시키는 함수입니다.</li>
<li>현재의 상태와 액션을 통해 새로운 상태를 반환합니다.<pre><code class="language-javascript">const counter = (state,action) =&gt; {
switch(action.type) {
  case &#39;INCREASE&#39;:
    return state + 1;
  case &#39;DECREASE&#39;:
    return state - 1;
  default:
    return state;
}
}</code></pre>
</li>
</ul>
<h3 id="4-store">4. Store</h3>
<ul>
<li>스토어는 상태와 리듀서를 포함하는 객체입니다.</li>
<li>스토어는 앱에서 단 하나만 가질 수 있습니다.</li>
</ul>
<h3 id="5-dispatch">5. dispatch</h3>
<ul>
<li>dispatch는 액션을 발생시키는 스토어의 내장함수 중 하나입니다.</li>
</ul>
<h2 id="redux-체험하기">Redux 체험하기</h2>
<ol>
<li>리덕스 모듈을 만든다.<ul>
<li>리덕스 모듈은 액션타입, 액션생성함수, 리듀서를 포함하는 파일입니다.</li>
</ul>
</li>
</ol>
<pre><code class="language-javascript">/* Ducks 패턴 액션 타입 */

const SET_DIFF = &#39;counter/SET_DIFF&#39;;
const INCREASE = &#39;counter/INCREASE&#39;;
const DECREASE = &#39;counter/DECREASE&#39;;

/* 액션 생성 함수 만들기 */

export const setDiff = diff =&gt; ({ type: SET_DIFF, diff });
export const increase = () =&gt; ({ type: INCREASE });
export const decrease = () =&gt; ({ type: DECREASE });

/* 초기 상태 선언 */
const initialState = {
    number: 0,
    diff: 1
};

/* 리듀서 선언 */

export default function counter(state = initialState, action) {
    switch (action.type) {
        case SET_DIFF:
            return {
                ...state,
                diff: action.diff
            };
        case INCREASE:
            return {
                ...state,
                number: state.number + state.diff
            };
        case DECREASE:
            return {
                ...state,
                number: state.number - state.diff
            };
        default:
            return state;
    }
}</code></pre>
<ol start="2">
<li>모듈을 묶어 최상단 index.js에 적용한다.</li>
</ol>
<pre><code class="language-javascript">const store = createStore(rootReducer);

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;
);</code></pre>
<ol start="3">
<li>UI 컴포넌트와 컨테이너 컴포넌트를 만든다.</li>
</ol>
<pre><code class="language-javascript">function CounterContainer() {
    // useSelector는 리덕스 스토어의 상태를 조회하는 Hook입니다.
    // state의 값은 store.getState() 함수를 호출했을 때 나타나는 결과물과 동일합니다.
    const { number, diff } = useSelector(state =&gt; ({
        number: state.counter.number,
        diff: state.counter.diff
    }), shallowEqual);

    // useDispatch 는 리덕스 스토어의 dispatch 를 함수에서 사용 할 수 있게 해주는 Hook 입니다.
    const dispatch = useDispatch();
    // 각 액션들을 디스패치하는 함수들을 만드세요
    const onIncrease = () =&gt; dispatch(increase());
    const onDecrease = () =&gt; dispatch(decrease());
    const onSetDiff = diff =&gt; dispatch(setDiff(diff));

    return (
        &lt;Counter
            // 상태와
            number={number}
            diff={diff}
            // 액션을 디스패치 하는 함수들을 props로 넣어줍니다.
            onIncrease={onIncrease}
            onDecrease={onDecrease}
            onSetDiff={onSetDiff}
        /&gt;
    );
}

export default CounterContainer;

// 각 컴포넌트는 서로 다른 파일입니다.


const Counter = ({ number, diff, onIncrease, onDecrease, onSetDiff }) =&gt; {
    const onChange = e =&gt; {
        onSetDiff(parseInt(e.target.value, 10));
    };

    return (
        &lt;div&gt;
            &lt;h1&gt;
                {number}
            &lt;/h1&gt;
            &lt;div&gt;
                &lt;input type=&quot;number&quot; value={diff} min=&quot;1&quot; onChange={onChange} /&gt;
                &lt;button onClick={onIncrease}&gt;+&lt;/button&gt;
                &lt;button onClick={onDecrease}&gt;-&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}

export default Counter;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] 매개변수와 함수, 화살표 함수 return 없이도 쓰는 법]]></title>
            <link>https://velog.io/@nowod_it/Javascript-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EC%99%80-%ED%95%A8%EC%88%98-%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98-return-%EC%97%86%EC%9D%B4%EB%8F%84-%EC%93%B0%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@nowod_it/Javascript-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98%EC%99%80-%ED%95%A8%EC%88%98-%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98-return-%EC%97%86%EC%9D%B4%EB%8F%84-%EC%93%B0%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Mon, 01 Aug 2022 04:53:09 GMT</pubDate>
            <description><![CDATA[<h2 id="함수가-매개변수로-넘어오는-경우">함수가 매개변수로 넘어오는 경우</h2>
<p>Javascript 코드들을 보다보면 함수를 매개변수로 넘겨주는 경우들이 종종 등장한다.</p>
<p>arr.filter(function);
arr.forEach(function);</p>
<p>대개는 아래 처럼 화살표 함수를 직접 넣어서 사용한다.</p>
<pre><code class="language-javascript">arr.filter(val =&gt; {return val &gt; 10});
arr.forEach(val =&gt; {console.log(val)});</code></pre>
<p>그런데 함수를 통째로 넘기는 경우도 볼 수 있다.
자바스크립트의 특징덕분에 함수만 넘기면 알아서 배열속의 값들이 전달된다.</p>
<pre><code class="language-javascript">const testFunc = (x) =&gt; {
  console.log(x);
}

arr.forEach(testFunc);</code></pre>
<p>그런데 만약 함수가 여러개의 인자를 갖고 있어야 되는 경우에는 어떻게 해야할까?
단순히 아래처럼 넘기면 a는 값, b는 인덱스 값이 넘어간다.
결국 화살표 함수를 사용할 때 값을 넘기는 것 처럼 넘어간다.</p>
<pre><code class="language-javascript">const testFunc = (a,b) =&gt; {
  console.log(`a : ${a}`); // a : value
  console.log(`b : ${b}`); // b : index
  console.log(`a-b : ${a-b}`);
}

arr.forEach(testFunc);</code></pre>
<p>그럼 함수 자체적으로 이미 매개변수가 있고, 그와 별개로 배열의 값을 받아서 처리하고 싶다면 어떻게 해야할까?
이럴 때는 클로저 개념을 가져와서 사용하면 원활하게 풀 수 있다.</p>
<pre><code class="language-javascript">const testFunc = (a,b) =&gt; {
  // X는 클로저로 인해 배열 내부 값을 가져온다.
    return function(x) {
      return (x - a + b)
    }
}

arr.forEach(testFunc(10, 20));</code></pre>
<h2 id="화살표-함수를-return-없이-쓰는법">화살표 함수를 return 없이 쓰는법</h2>
<p>보통 화살표 함수를 쓰게 될 때 아래처럼 사용한다.</p>
<pre><code class="language-javascript">const testFunc = () =&gt; {
  return &#39;testFunc!!&#39;
}

testFunc();</code></pre>
<p>그런데 중괄호(&#39;{}&#39;) 대신 괄호 (&#39;()&#39;)를 쓰게 되면 return을 생략할 수 있다.
괄호를 아예 안쓰는 경우도 자동 return이 되기에, 생략가능하다.</p>
<pre><code class="language-javascript">// 바로 testFunc!! 문자를 리턴한다.
const testFunc = () =&gt; (&#39;testFunc!!&#39;);
const testFunc2 = () =&gt; &#39;testFunc!!&#39;;

testFunc();
testFunc2();</code></pre>
<p>중괄호 없이 괄호만 있는 함수를 만나더라도 당황하지말고 중괄호와 괄호의 차이를 기억하자!</p>
]]></description>
        </item>
    </channel>
</rss>