Skip to main content

ChatGPT Automate Task Using Python

· 4 min read
Alex Han
Software Engineer

배경

ChatGPT 에 대해 일론머스크가 ChatGPT 무섭다고 한 기사를 읽고 간단히 사용해 본 후기.

ChatGPT Automation

ChatGPT 란 무엇인가?

ChatGPT는 OpenAI가 개발한 프로토타입 대화형 인공지능 챗봇이다. GPT-3.5 언어모델 기반으로 만들어졌고 지도학습, 강화학습 모두 사용해 파인 튜닝되었습니다.(현재 기준이고 계속 발전될 걸로 보임.)

사용 예시

matplotlib

chatGPT 싸이트에 try chatgpt를 클릭해 바로 사용할 수 있습니다.(로그인을 해야 하므로 사전에 가입해야 함.) 로그인을 하고 나면 채팅창 같이 뜨는데 채팅 창에 plot a linear regression with Python using matplotlib 을 쳐 보았습니다.

chatgpt_matplotlib

import matplotlib.pyplot as plt

# create some fake data
x = [1, 2, 3, 4, 5]
y = [2, 3, 4, 5, 6]

# fit a linear regression model
slope, intercept = np.polyfit(x, y, 1)

# predict the y-values of a line with the fitted model
predictions = [slope * i + intercept for i in x]

# plot the data points and the fitted line
plt.scatter(x, y)
plt.plot(x, predictions)

plt.show()

위와 같이 개발을 위한 코드를 생성해 줍니다.

실제로 사용 가능한지 체크 해 보면 np 가 undefined 로 뜨지만 이 정도는 matplotlib, pandas, numpy를 써 봤다면 자주 보는 축약어 numpy의 np 임을 바로 유추할 수 있습니다. 그래서 numpy를 import해 실행해 보면 정상 동작함을 볼 수 있습니다.

matplotlib_codetest

send message

이번엔 whatsapp 으로 메시지를 보내도록 send a message on Whatsapp using Python and pywhatkit 쳐 봤습니다.

send_whatsapp

기존에 구글에서 검색해 스택오버플로우를 찾거나 공식 문서를 뒤적여 봐야 했지만 이제는 라이브러리를 어떻게 사용하는지까지 한줄 타이핑으로 알 수 있습니다.

scraping

이번엔 웹싸이트를 스크래핑을 위해 web scrape https://books.toscrape.com/ using Python and beautiful soup 를 쳐보자.(해당 명령은 잘 동작하지만 다른 웹 싸이트는 잘 동작하지 않을 수 있음. 사용 방법은 맞게 구현됨.)

webscrape_gpt

실제 코드에서 실행해 보면

webscrape

잘 동작합니다.

결론

무료로 배포되어 있기 때문에 구글 검색이 귀찮고 따분해진 사람들은 새로운 검색엔진 형태로 사용해 보는 것도 좋아 보입니다.(개인적으로는 마이크로소프트에서 만든 copilot 보다 나아 보임.) 인공지능의 자연어 처리 모델이 얼마나 발전한지 경험해 볼 수 있는 귀한 시간이었습니다.

How to Train and Optimize A Neural Network

· 7 min read
Alex Han
Software Engineer

배경

deep learning을 할 줄 아냐고 항상 부담이 된다. 이름부터 어려워 보이는 deep learning 에 대해 pytorch 라이브러리를 활용해 간단히 사용 방법을 알아보자.

Deep Learning

딥러닝은 머신 러닝의 하위 집합으로, 특히 사람의 뇌 구조와 기능에서 영감을 얻은, 알고리즘을 포함한 대량의 데이터를 다룹니다. 그래서 딥러닝 모델을 종종 심층 신경망이라고 부르는 것입니다.

Dataset

복잡하고 수 많은 작업이 필요한 데이터 전처리 과정들은 제외하기 위해 small iris dataset 활용.

Load Data with Data Loader

import warnings
warnings.filterwarnings("ignore")

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader, TensorDataset
from sklearn.model_selection import train_test_split

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from IPython import display
display.set_matplotlib_formats("svg")

iris = pd.read_csv("https://gist.githubusercontent.com/netj/8836201/raw/6f9306ad21398ea43cba4f7d537619d0e07d5ae3/iris.csv")
iris.head()

head() 메서드를 활용해 dataset의 컬럼과 실제값을 간단히 확인합니다.

iris_head

딥러닝을 통해 예측할 목표는 variety 컬럼이다. 다른 4개의 컬럼에 따라 이 컬럼의 값이 변경된다고 보면 됩니다. 수식으로 생각하면 4개의 컬럼을 X, 목표로 하는 예측값을 y로 해서 생각해 봅니다.

X = torch.tensor(iris.drop("variety", axis=1).values, dtype=torch.float)
y = torch.tensor(
[0 if vty == "Setosa" else 1 if vty == "Versicolor" else 2 for vty in iris["variety"]],
dtype=torch.long
)

print(X.shape, y.shape)

위의 코드와 같이 불러온 데이터에서 variety 컬럼을 제거한 것을 X, variety 만 불러온 것을 y로 합니다. 이 때 variety 컬럼의 값들 중 Setosa를 0, Versicolor를 1, 나머지를 2로 변환해 줍니다.

Train / Test split

이제 training할 데이터들과 실제 이를 검증할 test 데이터들을 나누어 줍니다. 미리 import 해 둔 from sklearn.model_selection import train_test_split 라이브러리를 사용하면 됩니다.

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.8, random_state=42)

train_data = TensorDataset(X_train, y_train)
test_data = TensorDataset(X_test, y_test)

train_loader = DataLoader(train_data, shuffle=True, batch_size=12)
test_loader = DataLoader(test_data, batch_size=len(test_data.tensors[0]))

print("Training data batches:")
for X, y in train_loader:
print(X.shape, y.shape)

print("\nTest data batches:")
for X, y in test_loader:
print(X.shape, y.shape)

Training

딥러닝을 위해 만들 모델은 input layer과 output layer를 연결하는 16개의 노드와 단일 hidden layer를 활용합니다.

class Net(nn.Module):
def __init__(self):
super().__init__()
self.input = nn.Linear(in_features=4, out_features=16)
self.hidden_1 = nn.Linear(in_features=16, out_features=16)
self.output = nn.Linear(in_features=16, out_features=3)

def forward(self, x):
x = F.relu(self.input(x))
x = F.relu(self.hidden_1(x))
return self.output(x)


model = Net()
print(model)

모델을 만들었으니 이제 training을 시작하는데 반복 동작하면서 오차를 줄여가는 과정을 수행하게 됩니다. Crossentropyloss를 사용해 오차를 추적하고 Adam으로 경사 하강 과정을 진행합니다.

num_epochs = 200
train_accuracies, test_accuracies = [], []

loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)

for epoch in range(num_epochs):
# Train set
for X, y in train_loader:
preds = model(X)
pred_labels = torch.argmax(preds, axis=1)
loss = loss_function(preds, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_accuracies.append(
100 * torch.mean((pred_labels == y).float()).item()
)

# Test set
X, y = next(iter(test_loader))
pred_labels = torch.argmax(model(X), axis=1)
test_accuracies.append(
100 * torch.mean((pred_labels == y).float()).item()
)

데이터가 작아 금방 완료되는데요. 반복 동작의 횟수 당 정확도가 어떻게 나왔었는지 시각화를 통해 보게 되면 아래와 같습니다.

fig = plt.figure(tight_layout=True)
gs = gridspec.GridSpec(nrows=2, ncols=1)

ax = fig.add_subplot(gs[0, 0])
ax.plot(train_accuracies)
ax.set_xlabel("Epoch")
ax.set_ylabel("Training accuracy")

ax = fig.add_subplot(gs[1, 0])
ax.plot(test_accuracies)
ax.set_xlabel("Epoch")
ax.set_ylabel("Test accuracy")

fig.align_labels()
plt.show()

visualize

Optimization

  1. 우선 트레이닝 과정을 아래와 같이 단순화합니다.
losses = []
def train_model(train_loader, test_loader, model, lr=0.01, num_epochs=200):
train_accuracies, test_accuracies = [], []
loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr=lr)

for epoch in range(num_epochs):
for X, y in train_loader:
preds = model(X)
pred_labels = torch.argmax(preds, axis=1)
loss = loss_function(preds, y)
losses.append(loss.detach().numpy())
optimizer.zero_grad()
loss.backward()
optimizer.step()

train_accuracies.append(
100 * torch.mean((pred_labels == y).float()).item()
)

X, y = next(iter(test_loader))
pred_labels = torch.argmax(model(X), axis=1)
test_accuracies.append(
100 * torch.mean((pred_labels == y).float()).item()
)

return train_accuracies[-1], test_accuracies[-1]


train_model(train_loader, test_loader, Net())
  1. layer 개수를 조절할 수 있는 새로운 모델 클래스를 생성합니다.
class Net2(nn.Module):
def __init__(self, n_units, n_layers):
super().__init__()
self.n_layers = n_layers

self.layers = nn.ModuleDict()
self.layers["input"] = nn.Linear(in_features=4, out_features=n_units)

for i in range(self.n_layers):
self.layers[f"hidden_{i}"] = nn.Linear(in_features=n_units, out_features=n_units)

self.layers["output"] = nn.Linear(in_features=n_units, out_features=3)

def forward(self, x):
x = self.layers["input"](x)

for i in range(self.n_layers):
x = F.relu(self.layers[f"hidden_{i}"](x))

return self.layers["output"](x)
  1. layer를 1~4개까지 증가시켜 보고 각 layer가 8, 16, 24, 32, 40, 48, 56개 노드를 각각 가졌을 때 어떻게 변화하는지 지켜봅니다.
n_layers = np.arange(1, 5)
n_units = np.arange(8, 65, 8)
train_accuracies, test_accuracies = [], []

for i in range(len(n_units)):
for j in range(len(n_layers)):
model = Net2(n_units=n_units[i], n_layers=n_layers[j])
train_acc, test_acc = train_model(train_loader, test_loader, model)
train_accuracies.append({
"n_layers": n_layers[j],
"n_units": n_units[i],
"accuracy": train_acc
})
test_accuracies.append({
"n_layers": n_layers[j],
"n_units": n_units[i],
"accuracy": test_acc
})


train_accuracies = pd.DataFrame(train_accuracies).sort_values(by=["n_layers", "n_units"]).reset_index(drop=True)
test_accuracies = pd.DataFrame(test_accuracies).sort_values(by=["n_layers", "n_units"]).reset_index(drop=True)
test_accuracies.head()

accuracy

결과를 보면 테스트 정확도는 layer가 몇 개일 때 node가 몇 개일 때 어떤 양상을 보였는지 알 수 있고 test_accuracies[test_accuracies["accuracy"] == test_accuracies["accuracy"].max()] 를 통해 이 중 가장 정확도가 높은 layer, node 개수를 선정할 수 있습니다.

결론

iris dataset은 굉장히 적은 데이터이고 training이 굉장히 잘 되는 데이터이기에 이 과정이 무의미해 보이도록 정확도가 대부분이 정확도가 높게 나왔습니다.

하지만 실제 업무에서는 이처럼 깨끗하지 않고 깨끗하더라도 경향성이 일관되지 않아 그 수식을 찾아내기 힘든 경우가 대다수기 때문에 이와 같은 과정들이 필요합니다. layer, node 개수를 조정해 보는 것 외에도 변수들을 조절해 모델에 변화를 주거나 training 방식의 변화를 주어 optimization을 한다면 보다 의미있는 값들을 도출할 수 있을 거라 생각합니다.

이 글을 쓰면서 딥러닝에 좀 더 자신감을 가질 수 있으면 좋겠다 나도..;

Tech Lead In Start-up(스타트업에서의 테크리드 역활에 대한 고찰)

· 19 min read
Alex Han
Software Engineer

배경

많은 회사를 거치진 않았지만 작은 스타트업에서 개발해 왔기 때문에 직접 기술적인 리딩을 할 경험도 주변에서 리드하는 모습도 가까이서 볼 수 있었는데, 그 역할에 대한 생각을 적어보려고 합니다.

Tech Lead

테크리드란 무엇인가?

테크 리드란 개발 리더, 테크니컬 리더, 리드 프로그래머, CTO 등 회사마다 부르는 용어는 다르지만 그 의미는 개발자들의 매니저로서 개발 분야 기술의 책임자입니다.

역할의 범위는 어디까지?

작은 스타트업에 주로 근무할 때를 떠올리면, 경영진과 개발자들 모두 처음에 원했던 테크 리드의 역할은 사내에서 사용하는 모든 기술을 다 이해하고 직접 구현할 수도 있으면서 개발 시 어려운 부분이 있다면 해결사 역할도 해주고 조언도 해주며 경영진과 개발자 간의 불편한 소통을 대신해 주어 기술적 난이도를 정확히 말해주고 일정 조율도 해주는 것이었습니다. 이 많은 역할을 경험이 많아서 입사한지 한 달 안에 프로처럼 처리해 주는 모습을 기대합니다.

위와 같은 기대는 사실 허상이기도 하고 업무를 떠넘기고자 하는 욕구이기도 합니다.

자연스러운 스타트업에서의 테크리드!

작은 스타트업이면서 개발 인력이 적은 곳에 개발자들은 1명이 여러 명의 역할을 해줘야 합니다.

예를 들어 백엔드 개발자는 백엔드 서버는 물론 클라우드, 인프라 개발, 데이터베이스 관리, 데이터 엔지니어링, 분석, 크롤러, CI/CD, 문서 작성, 스케줄 관리, BtoB 미팅, 디자인/기획 협의 등 사실 선임 개발자고 밑에 개발 인원이 조금 더 있다면 자연스럽게 후임 개발자들과 업무를 나누며 백엔드 테크리드들이 하는 역할을 직접하게 됩니다.

거기에 더해 인력이 더 적을 시는 프론트엔드, 앱 개발도 병행해서 해야 되는 상황도 발생할 수 있습니다.

그렇게 시간이 지나 개발자들 간의 체계가 잡히기 시작하면 깃허브 이슈 관리, 코드 리뷰, 테스트 프로세스, 개발 컨벤션, 개발/배포 절차 등 많은 정의들을 테크 리더가 만들고 이에 맞춰 개발자들은 문서와 개발이 일체화 되어 가며 하는 일이 명확해집니다.

이렇게 되면 리드를 당하는 개발자들은 자기 개발만 신경쓰고 타 개발자들이 문서화하고 이슈, 태스크 등에 등록한 자료들을 통해 자료도 쉽게 찾아 사용할 수 있는 효율화된 개발 조직이 됩니다.

자연스럽게 테크리더들은 경영진들과 논의하며 다음 비지니스들에 대해 기술적 연구를 하고 개발자들과 상의해 가며 일정을 산출하고 매니징하기 시작하고 회사에서는 경험이 풍부한 시니어 개발자들을 더 채용하고 테크리더들은 소통하며 더 좋은 문화를 만들어 갑니다.

부자연스러운 스타트업에서의 테크리드?

**"자연스러운 스타트업에서의 테크리드"**는 기존 개발자들을 더욱 규합하고 서비스 운영 노하우와 개발 경험들울 가지고 시작할 수 있습니다.(다음에 들어오는 사람들에 대한 배려와 포용할 수 있는 개발 문화를 가지지 않으면 문제가 되겠지만...)

보통 초기 스타트업은 돈이 많지 않기 때문에 개발자는 주니어를 채용하고 이조차도 많은 인원을 채용하지도 않으며 기획자, 디자이너, 마켓터 등 개발을 원하는 비개발자들을 채용합니다.

주니어 개발자들은 열심히 서비스를 구현하고 런칭까지 성공시키면서 빠르게 성장하지만 당연하게도 개발 속도가 느렸을 거고 자신 없는 부분들을 드러내기도 했을 겁니다.

그러나 성공적인 런칭과 운영을 하며 다른 기술들을 공부하고 그 회사에 맞는 개발자들 간 프로세스들을 조금씩 확립해 나갈 즈음, 소수 인원의 개발자들은 힘에 부치기도 하고 잘하고 있는지 불안하기도 한 마음에 테크리드 채용을 원하기도 하고, 경영진들은 주니어 개발자들을 돕기 위해서 혹은 신뢰하지 못해서 "역할의 범위는 어디까지?" 에서 말한 허상에 가까운 테크리드 채용에 기대를 걸기 시작합니다.

그렇게 기대를 한 몸에 받고 들어온 테크리드들은 그 동안 구현해 놓은 개발 산출물들을 공부할 시간이 필요하지만 빠른 적응을 요구하기 시작합니다.(문서화가 많이 되어 있지 않고 개발 상태가 좋지 않을수록 더 많은 시간이 소요됨.) 그 동안 자연스럽게 테크리드를 해 오던 성장한 개발자들은 본인이 하던 일은 계속 하면서 테크리더로 들어온 인원에게 지속적인 교육까지 하게 되면서 일은 더 가중됩니다.

경력과 경험이 많은 테크리드 분이라도 사내에서는 그가 서비스 구성과 개발 현황, 기술들을 파악할 시간을 주어야 하는데 서비스가 빠르게 변화해야 하는 스타트업에서 이를 기다려주기는 힘듭니다. 묵묵히 파악하며 기술에 대한 조언 정도를 해야 하는 상황에 현재의 스케줄 조율과 이슈 트랙킹 등 많은 것들을 과중하기 시작하면서 제대로 파악하지 못한 상태는 지속되고 이런 상태로 선택과 조율, 협의가 계속 잘못 이뤄져 갑니다.

반대로 경험 많은 시니어 개발자를 테크리드로 채용했다면 높은 급여를 보장해 줘야 했을 것이기 때문에 테크리드를 위한 업무 파악을 시키는데 있어서도 회사적으로도 큰 손실이 됩니다.

차라리 위에서 말한 "자연스러운 스타트업에서의 테크리드" 의 리더가 경험 많은 시니어 개발자와 의견을 조율해 가며 기존 팀원들과 소통을 잘 해가며 업무 조율을 하는 것이 유리하다고 생각이 듭니다. 아니면 시니어 개발자로 채용해 어느정도 업무를 같이 해보고 추후 하고자 하는 의지와 역량에 따라 주변 개발자들과 조율해 이를 결정한다면 보다 나은 방법이 될 수도 있습니다.

테크리드에 대한 경험

첫 IT 회사에서 앱 개발자, 백엔드 개발자, 프론트엔드 개발자(나)로 개발자 3명이 있었는데 백엔드 개발자 분이 그 회사의 그나마 테크리드로서 역할을 했었습니다.

10년 넘는 경력이 있던 그 분은 react에 대한 경험이 적지만 최신 기술이라는 이유로 react를 도입했는데 연구 없이 도입해 사용 방법에 대해 잘 몰랐고 내가 입사하기 전에는 2명이서 개발을 해왔기 때문에 누군가에게 공유하는 일 또한 해보지 않았던 분이라 문서도 공유도 교육도 없는 분 이었습니다.

지금 생각해 보면 개발자로서 누군가한테 의지하지 말고 스스로 공부해서 처리해야 하는구나를 호되게 배웠던 회사였기에 어느 정도 개발자 인생에 대해 리드를 해주신건가 싶기도 합니다.

두번째 IT회사는 전체 인원이 같은 날짜에 입사해 모두 신입인데 첫 서비스를 만들기 위해 미션 수행을 하던 회사였는데, 이 회사에 같이 시작했던 빅데이터 대학원생, 데이터 엔지니어, 디자이너, 웹개발자 4명이서 개발을 시작했습니다.

이 중에서 그나마 서버 개발을 해 봤고 react 개발 경험이 있어 react-native 개발을 통해 앱도 개발할 가능성이 있었던 제가 자연스럽게 테크리드 역할을 맡게 됐습니다. 처음으로 인원들 마다의 업무를 분배하고 업무 난이도를 고려해 일정도 산출하고 그 스케줄을 조율하면서 문제가 있을 때마다 같이 고민하며 해결하는 재미가 있었습니다.

이 때 가장 힘들었던 건 부족한 경험에서 오는 자괴감이었습니다. 같은 직급이었지만 팀원들 모두 리딩에 잘 따라와 줬는데 클라우드, react-native, react 초기 설정, 백엔드 초기설정 등 모추 처음이었기 때문에 시간이 오래 걸렸고 여유가 없었습니다. 게다가 일정을 맞춰야 했기 때문에 분배한 업무가 잘 되지 않았을 때 동료들에게 가이드 해주거나 교육해 주지 않고 혼자서 업무 처리를 해 버렸습니다.

지금 생각해 보면 동료들의 성장과 나의 성장에도 회사를 위해서도 잘했던 행동은 아닌 것 같습니다. 하지만 여유가 없었던 주니어로서 리드 역할을 맡았으니 최선을 다 했던 걸로...

그 다음 회사에서는 입사하고 업무 교육을 잠시 받던 중 1달 동안 개발자들이 이해관계와 정치 싸움에 의해 모두 퇴사해 혼자 남았습니다. 6개월 동안 회사에 혼자 남아 개발 현황을 파악하며 유지보수하고 운영하고 업그레이드 했습니다. 그 동안 서비스를 위해 필요한 계정정보들부터 현재의 개발 상태, 배포 구성 등 운영과 개발에 필요한 문서들을 작성하기 시작했고 6개월 뒤 CTO로 입사한 분에게 이를 모두 인계 했습니다.

하지만 CTO 분은 입사하고 인계한 내용에 대해 공부하고 싶어하지 않았고 본인이 아는 스택으로 모두 재편하고자 했는데 이에 대해 데이터베이스 마이그레이션부터 개발 스택 선정에 대한 연구도 없이 강행 했습니다. 그 분의 리드 방식은 자신이 무엇을 하겠다는 말을 주변에 말하지 않고 혼자 갑자기 신기술에 대해 공부한 것이 있다며 실 서비스에 일정 부분 도입하고 전체 서비스에 팀원들이 마무리 해주길 원했습니다.

기존 서비스에 대해 전혀 파악하지 않고 신기술 적용 공부하듯 서비스에 도입하다 보니 ERD도 없고 데이터 마이그레이션도 하지 않아 기존 3년 간 모은 데이터들은 모두 손실됐고 비지니스와 직결됐던 배정 기능까지 문제가 생기면서 큰 문제들이 계속 발생됐고 발생한 문제들은 전체 그림을 공유하지 않았기 때문에 그 리더분에 의해서 해결되길 모두 기다리는 상황이었습니다.

그 CTO분은 결국 쫒겨나듯 나가는데 알고 보니 처음 말했던 거의 10년 경력은 실제로는 5년도 안되는 경력이었고 그 조차도 프론트엔드 경력이 대부분이었습니다. 거의 사기를 당한 경영진은 이후 CTO는 신중히 뽑겠다 했지만 결국 갑자기 게임업계 DBA로 계시던 분을 CTO로 영입하더니 모든 것을 인계해서 그 분을 구심점으로 돌아가길 원했습니다.

하지만 그 CTO분은 게임업계 개발 스택과 웹, 앱 개발 쪽의 개발 스택이 전혀 달랐고 DBA로서 DB만 보시던 분이었는데 역시 얼마 지나지 않아 게임업계로 돌아갔습니다.

시간이 지나 많은 분들이 입사하고 여유가 생기면서 개발도 하면서 자연스럽게 테크리드 업무들을 하게 됐습니다. 개발 과제에 대해 분석하고 타 팀과 협의 후 개발 팀 내 인원 분배, 일정 조율 등 전반적인 매니징을 진행하게 됐고, 또한 신기술 연구, 개발 스택 비교 등을 동료들에게 공유하고 함께 소통하며 근거 있는 개발 스택 선정을 했고 자연스럽게 팀원들과 소통해 깃허브 정책, 코드 컨벤션 등 개발 문화를 선진적으로 이끌 수 있었습니다.

이와 같은 경험들을 토대로 프리랜서로서도 테크리드 업무를 담당해 보다 나은 개발 정책과 문화를 자리 잡을 수 있도록 노력하고 있습니다. 이를 통해 크게 보고 매니징하며 더 여유있게 기술적으로 리딩할 수 있도록 더욱 성장하고자 하는 동기부여가 되는 것 같습니다.

마지막은 자기자랑처럼 끝나긴 했지만 아직 부족한 부분이 많다는 것은 리딩을 할수록 계속 느끼고 있고 꾸준히 공부하며 정말 팀에 필요한 것들을 찾아내 적재적소에 도입하고 모두와 좀 더 잘 소통하는 좋은 리더가 될 수 있게 노력할 것입니다.

AES + RSA Encryption(암호화 연구)

· 10 min read
Alex Han
Software Engineer

Untitled

배경(기존)

AES-256 FUI using CBC mode

이전 회사에서는 암호화 방식은 AES-256-CBC 방식을 사용하고 있고 사용자를 판별하기 위한 토큰 암호화에만 이를 사용했습니다.

암호화에 대해 잘 모르고 있었기 때문에 단순히 key, iv 값을 통해 암호화 전문(string)을 생성하고 key, iv 값을 통해 복호화 전문(string)을 만들어 사용했습니다.

기존의 암호화 사용방식은 아래와 같았습니다.

  1. 암호화 로직을 꼬는 방식
    1. 암호화 하는 방식
      1. 랜덤으로 AES 암호화 키 세트 생성.
      2. a의 키 세트를 서버의 환경 변수를 통해 암호화.
      3. 평문을 a에서 생성한 암호화 키 세트를 통해 암호화(token 생성).
      4. 데이터베이스에 b에서 암호화된 암호화 키 세트와 c를 통해 암호화 된 token 저장.
    2. 암호화 푸는 방식
      1. 데이터베이스에서 2-b 암호화된 암호화 키 세트를 가져와 서버의 환경변수를 통해 암호화를 풀어줌.
      2. a의 암호화 키 세트를 통해 2-c token 암호를 풀어 고객을 찾아내는 방식.

원래 있었던 평문을 암호화하고 암호화 문을 평문화하는 2가지 모듈에 로직을 약간 넣어 혹시 모를 키 유출에 대비하려고 했었습니다.

그러던 중 외부 금융권 회사(보안 중요)와 협업을 하게 됐는데, 그 회사에서는 암호화 키 노출에 대해 민감히 반응했고 암호화 방식 하나만이 아닌 다른 암호화 방식도 추가해 도입하길 원했습니다.

고객사의 요청 사항은 아래와 같았습니다.

비대칭 암호화를 통해 대칭키를 암호화해 주고 받고 통신 시 암호화해서 주고 받았으면 하는데 이 때 서로 다른 암호화 방식 2개(AES, RSA)를 사용했으면 한다는 것이었습니다.

AES 암호화 방식도 겨우 쓰고 있었기에 무슨 소리인지 알지 못했고 검색을 통해 AES 암호화 방식이 대칭키이고 RSA 암호화 방식이 비대칭키인 것을 알게 됐습니다.

AES 암호화는 암호화를 하거나 풀 때 동일한 키를 통해 암복호화를 진행하기 때문에 대칭키고 RSA 암호화는 public key, private key 중 하나로 암호화 하면 다른 하나로 복호화하는 방식이기에 비대칭키인 것으로 이해했습니다.

결론적으로 고객사의 니즈를 정확히 파악하다가 결국 간단한 프로세스를 만들어 냈습니다.

  1. 데이터를 주고 받을 시 AES로 암호화 하여 주고 받습니다.
  2. RSA로 AES 암호화 키를 암호화해 주고 받아 AES 암호화 키 유출을 막습니다.

RSA + AES 암호화 구성

위의 프로세스를 구성하기 위해서는 RSA 암호화 모듈, AES 암호화 모듈, 교체 주기에 따라 암호화 키들을 생성 업데이트 하는 모듈이 필요했고 아래와 같이 설계할 수 있었습니다.

RSA+AES 암호화 빙식 프로세스 RSA+AES 암호화 빙식 프로세스

역할

  1. RSA 암호화 모듈
    1. RSA 암호화 키 세트 랜덤 생성
    2. RSA encode
    3. RSA decode
  2. AES 암호화 모듈
    1. AES 암호화 키 세트 랜덤 생성
    2. AES encode
    3. AES decode
  3. AES 교체 주기마다 AES 암호화 모듈에 요청해 데이터베이스에 교체해주는 모듈
  4. RSA 교체 주기마다 RSA 암호화 모듈에 요청해 데이터베이스에 교체해주는 모듈

과정

  1. RSA 암호화 모듈을 통해 저장해둔 rsa public key와 AES 암호화 키세트를 고객사에 제공(api)
  2. 고객사는 AES 암호화 모듈로 랜덤하게 생성한 AES 키세트를 이용해 body를 암호화.
  3. AES 암호화 키 세트를 rsa public key를 이용해 RSA 암호화.
  4. RSA 암호화 된 AES 암호화 키 세트와 AES를 통해 암호화 된 body를 서버로 전송
  5. 서버에서 저장해 둔 rsa private key를 통해 AES 암호화 키 세트를 복호화.
  6. 복호화한 AES 암호화 키 세트를 통해 암호화 된 body를 복호화.
  7. 반환 시는 고객은 이미 랜덤하게 생성한 rsa 암호화 이전 AES 암호화 키를 가지고 있기 때문에 6번에서 복호화한 AES 암호화 키를 그대로 사용해 암호화 해 body로 전송하면 됩니다.

프로세스를 통한 이점

  1. 매 요청 마다 AES 암호화 키를 랜덤하게 생성 보안에 좋음.
  2. RSA 암호화 키를 주기적으로 교체하고 고객사도 이 정보를 주기적으로 받아 처리해 보안에 좋음.
  3. 고객사와 서비스 서버 간에 랜덤하게 생성한 후 rsa public key를 통해 암호화한 AES 암호화 키 세트를 요청 시 주는 것 외에 키를 주고 받지 않아 보안에 좋음.
  4. 암호를 풀 수 있는 rsa private key는 db server 에만 존재해 통신 시 보안이 DB 서버의 보안과 같이 높아짐.
  5. 고객사의 요청을 충족시켜줌

로직 변경

모든 걸 api를 통해 처리하고 자동화하려던 위의 프로세스 설계는 고객사 측의 기존 니즈와 다르고 완전하지 못한 설계였기에 좀 더 보완이 필요했고 고객사에서 원하는 방향대로 아래와 같이 로직을 다시 변경했습니다.

  1. 고객사에서 사용할 언어에 맞게 암호화 모듈을 제공하고 이를 통해 rsa 암호화 키를 생성 후 우리에게 rsa_public_key를 메일로 제공합니다.
  2. 해당 키를 저장하고 고객사에서 요청 시 랜덤으로 aes_key를 생성하고 저장해 둔 rsa_public_key를 encode 해 고객사에 api를 통해 전달합니다.
  3. 고객사에서는 해당 키를 encoded_aes_key를 decode 해 저장하고 우리와 통신 시 aes_key로 https body 를 encode 해 우리 서비스에 요청합니다.
  4. 고객에 요청에 의해 랜덤하게 생성한 aes_key를 저장해둔 것을 이용해 body를 decode하고 서비스 로직 실행 완료 후 다시 encode해 고객에게 반환합니다.
  5. 언제든 rsa_key 또는 aes_key 업데이트는 고객사의 요청에 따라 교체할 수 있게 합니다.

초기 설계한 로직에서 이미 기능들은 모두 만들었기 때문에 해당 로직을 구현하는데 어려움은 없었고 고객사의 니즈를 제대로 충족할 수 있었습니다.

결론

암호화 방식에 대해서 좀 더 공부해 볼 수 있는 동기부여가 되는 시간이었고 그 동안 잘못 사용하고 있던 것들을 바로 잡을 수 있었습니다. 또한 여러 암호화 방식을 이용해 보안을 더 강화하는 재밌는 로직에 대해서도 알 수 있는 흥미로웠고 배울 수 있는 시간이었습니다.

Combination Optimization(조합 최적화 연구)

· 10 min read
Alex Han
Software Engineer

배경

수 많은 케이스들을 더해 각 케이스들을 조합해 목적값을 찾는데 이 때 각 케이스의 금액이 가장 저렴한 케이스를 찾아야 할 때 조합 최적화 방법이 필요합니다.

최적의 조합

연구 과제

먼저 조합을 하려면 조합하기 위한 경우의 수를 찾아내야 합니다.

예를 들어 A, B, C, D 특성이 있다고 하면,

A, B, C 는 조합에 사용될 케이스이고 D 는 가격 특성이라고 하면 A, B, C 의 값 범위는 0~1로 동일하다고 하면 A, B, C 가 합쳐져 만들어지는 케이스를 구해보면,

ABC
000
001
010
011
100
101
110
111

0~1(2개) X 2 X 2 => 8개의 케이스가 생깁니다.

이와 같은 케이스 생성 방법은 Cartesian Product를 활용하여 쉽게 도출할 수 있습니다.

이제 A, B, C 간의 조합을 통해 목적값을 구한다고 생각해 보면,(아래는 목적값)

ABC
221

이 목적값을 구하기 위해 아래와 같은 여러 개의 가능한 조합들이 생깁니다.

ABC
010
101
110
ABC
110
111

조합은 2개의 행으로 조합될 수도 있고 3개의 행으로 조합될 수도 있습니다. 이번 목적값과 데이터셋에서는 불가하지만 1개의 행으로도 충족하는 상황도 있습니다. 이 때 아래와 같이 D 컬럼이 가격이라고 하고 행 간 조합을 통해 합산된 가격이 가장 저렴한 조합을 찾는다고 해보면,

ABCD
0005
0013
0102
0111
1006
1014
1103
1111

가격이 위와 같을 경우 위의 조합이 가능할 때 합산 금액은 첫번째는 7, 두번째는 4인데 조합이 가능한 케이스가 2개의 조합뿐이라고 할 때 두번째 조합이 최적의 조합입니다.

ABCD
0102
1014
1111
ABCD
1103
1111

하지만 만약 조합 컬럼 개수가 10개 정도 된다면 그리고 컬럼마다의 값의 범위가 0~9 정도 된다면 Cartesian Product를 통해 10^10개의 조합 재료가 생깁니다. 이 조합 재료의 총 진부분집합(공집합을 제외한 부분집합) 개수는 자그마치 2^10000000000개가 됩니다. 2의 백억승이 되는데 16000승만 되도 이미 5천 자리를 넘어서게 됩니다. 이 같이 모든 조합을 구하는 방식은 조합 방식에 따라 가능, 불가능 여부를 따지게 됩니다.

목적 조합

위와 같이 모든 조합 케이스를 다 만드는 순간 왠만한 프로그램은 멈추거나 몇 일 뒤에 결과가 나는 등 아주 안 좋은 결과를 얻을 수 있습니다.

combination_way

그래서 위의 그림과 같이 모든 조합을 만들고 목적조합을 찾는게 아닌 역으로 조합의 목적값을 생성하는 방식으로 조합을 만들게 됩니다.

ABCD
0005
0013
0102
0111
1006
1014
1103
1111

2개 조합으로 생각하면 목적값이 111 이라고 가정하면, 첫행과 마지막행, 그리고 두번째행과 마지막에서 두번째행, ... 이런식으로 가운데까지 조합이 생깁니다.

3개 조합으로 생각하면 첫행과 둘째행과 마지막에서 두번째 행, 첫행과 세번째행과 마지막에서 세번째행, ... 이런식으로 조합이 생깁니다.

위와 같이 목적값에 따라 행열 구조에서 대각선의 일정 규칙을 지닌 조합들을 찾아낼 수 있습니다.

이렇게 하면 모든 조합 케이스를 찾는 말도 안되는 조합 개수를 생성해 목적값 충족하는지 여부를 조합들 하나 하나 계산해 봐야 하는 프로세스를 제외시키고 조합 마다의 가격 비교만 하면 최적의 조합을 찾아낼 수 있습니다.

다른 방법 소개

위의 조합 방법은 Google OR-Tools를 활용해 간단하게 해결할 수도 있습니다.

Google 신의 OR-Tools는 원래 C++로 작성됐는데 python으로 사용 가능하고 선형 계획법, 혼합 정수 계획법, 제약 조건 프로그래밍, 차량 경로 지정 및 관련 최적화 문제를 해결하기 위해 개발한 무료 오픈 소스인데 여기서 cp_model을 활용해 해당 문제들을 해결합니다.

아래는 pandas를 활용한 Cartesian Product로 데이터 셋을 활용하고 랜덤 가격을 적용한 후 Google Ortools의 cp_model을 활용해 간단히 조합 찾아내는 예제를 만들어낸 페이지 예시입니다. 숫자를 입력하고 랜덤을 누르면 입력한 숫자가 목적값이 되고 랜덤으로 생성된 가격의 최적 조합을 찾아냅니다.(react + fastapi 조합)

현재는 heroku 유료화로 백엔드가 정상동작되지 않아 추후 정상화할 예정

결론

위의 조합을 이용해 bin-packing 화면을 구현하려 했지만 시간도 늦었고 해야될 다른 작업들이 많아(귀찮아서) 하진 않았습니다. 이런 최적 조합을 찾는 방법들은 좀 더 최적화된 방법들을 사람들이 만들어낼 텐데 더욱 좋은 기술들이 나와 세상에 수 많은 최적 조합이 필요한 경우를 가장 효율적으로 해낼 수 있다면 좋을 것 같습니다.

혹시나 내가 최적의 조합을 만드는 알고리즘을 성장시키는데 일조한다면 그 또한 뿌듯할 거 같습니다.

Database Concurrency(데이터베이스 동시 배정 처리)

· 5 min read
Alex Han
Software Engineer

Untitled

배경

현업에서 업무를 하다 보면 배정 관련해서 데이터베이스 쿼리를 통해 가장 적게 배정 받은 순서로 플래너를 배정하고 안심번호를 배정하는 경우가 있습니다. 이때 요청이 많을 경우 문제가 발생합니다.

예를 들어 배정이 가장 적게 된 사람을 검색하고 그 사람은 이제 배정 됐음을 데이터베이스를 통해 업데이트를 하게 된다면, 이 프로세스를 진행하는 동안 수 많은 고객들이 데이터베이스가 업데이트 되기도 전에 같은 프로세스를 돌면서 중복 업데이트, 중복 배정의 문제가 발생됩니다.

설계

솔루션을 활용한 방법(rabbitmq, kafka, redis, …)

Untitled2

순서

  1. 백엔드로 요청이 들어오면 배정을 위해 필요한 데이터를 준비하고 그 순서를 큐에 쌓아둡니다.
  2. 쌓아둔 큐를 규칙에 따라 cron이 돌면서 가져와 순차적으로 배정됩니다.
  3. 이 때 큐의 규칙은 FIFO로 설정해 먼저 쌓인 요청부터 처리하는 방식이 신뢰도 있게 동작할 수 있습니다.

결론

메시지 큐 관련 솔루션을 활용해 배정이 필요한 부분을 쌓아둔 뒤, 일정 규칙에 맞게 cron을 돌려 쌓아둔 배정을 순차적으로 처리하는 방식입니다.

MySQL user-level lock 기능을 활용하는 방법

소개

MySQL은 사용자 레벨에서 데이터베이스를 조작할 수 있는 Lock 기능을 아래와 같이 제공합니다.

NameDescription
GET_LOCK()Get a named lock
IS_FREE_LOCK()Whether the named lock is free
IS_USED_LOCK()Whether the named lock is in use; return connection identifier if true
RELEASE_ALL_LOCKS()Release all current named locks
RELEASE_LOCK()Release the named lock

GET_LOCK(str, timeout): Lock의 이름을 설정하고 해당 락 프로세스 수행이 오래 걸릴 시를 대비해 timeout도 설정합니다.

IS_FREE_LOCK(str): GET_LOCK에서 설정된 이름을 조회해 현재 이 Lock이 사용중이라면 0, Lock이 풀렸다면 1, 만약 오류가 있다면 null 을 반환합니다.

IS_USED_LOCK(str): 이름에서 보는 것과 같이 IS_FREE_LOCK과 반대로 동작합니다.

RELEASE_ALL_LOCKS(): 해당 세션에서 설정한 모든 락을 풀어줍니다.

RELEASE_LOCK(str): Lock의 이름을 찾아 Lock을 풀어줍니다.

순서

Untitled1

동시성에 대해 문제가 발생하는 위의 그림과 같이 lock을 걸고 insert, update 를 처리한 후에 lock을 release 해주어 이를 해결하는 방법입니다.

  1. 배정이 가장 적게 된 사람을 검색하고 배정 됐음을 업데이트 하는 프로세스를 코드 실행 상 가장 근접하게 구성합니다.
  2. 1번에서 구성된 프로세스 앞 단에 GET_LOCK 쿼리를 하고 로직 수행 후 RELEASE_LOCK을 수행합니다.

결론

솔루션을 활용하는 방법 보다 서버 구성의 복잡도를 줄일 수 있고 백엔드 서버 내에서 코드적으로 처리할 수 있는 이점이 있습니다.

Process & Thread

· 7 min read
Alex Han
Software Engineer

Operating Architecture Operating Architecture

배경

누군가한테 질문을 받았다. 분명 파이썬에서 멀티 프로세스를 사용하거나 고랭에서 멀티 쓰레드를 사용해 성능을 향상 시켰었는데 어떻게 사용해 봤다만 이야기 했습니다.

모르는데 해봤다고 말하니 모르고 막 사용한 거 같은 부끄러움이 밀려왔다. 다음부터는 좀 더 알아보고 사용해야지 싶어 간단히 정리해 봅니다.

Process & Thread

Process

운영체제로부터 자원을 할당받는 작업의 단위로서 프로그램이 메모리에 올라가 실행 중인 상태가 될 때 그 상태 단위를 프로세스라고 합니다.

Thread

할당 받은 자원을 이용하는 실행의 단위로서 하나의 process 내에 여러 개의 thread 가 생길 수 있습니다.

결론

process는 작업을 수행하기 위한 단위이고 thread는 process를 수행하기 위한 단위입니다.

Multi Process & Multi Thread

Multi Process

여러 개의 process로 하나의 작업을 처리하는 것입니다.

하나의 process에서 오류가 생겨도 프로그램은 동작하는 장점이 있고 context switching 비용이 발생하는 단점이 있습니다.

Multi Thread

여러 개의 thread로 하나의 process를 처리하는 것입니다.

시스템 자원 소모 감소와 처리 비용 감소, thread 간 자원 공유 등의 장점이 있고 debugging이 어렵고 동기화 이슈 발생, 하나의 오류로 process 문제 발생시키는 단점이 있습니다.

결론

multi process, multi thread 방식의 큰 차이점은 multi thread의 경우 하나의 process 내부에서 데이터를 공유하며 동작할 수 있다는 이점이 있어 비용을 감소시키고 메모리 공간도 적게 사용해 효율적이다. multi process는 하나의 작업을 수행하기 위해 프로세스 간 context switching을 거치게 되는데 multi thread는 공유 자원을 가지고 있어 context switching에 들어가는 비용이 절감되는 장점도 있습니다.

위의 장점만으로 보면 multi thread의 장점만 보이지만 공유 자원을 이용하는 만큼 공유 자원에 동시 접근에 의한 오류들이 생길 수 있고 debugging 또한 까다로워 신중하게 고려해야 하는 까다로움이 있습니다.

Thread Safe & Context Switching

Thread Safe

여러 thread 가 동시 동작해도 안전하다는 의미로서 함수 A, B가 여러 thread에서 호출되도 하나의 thread에서 호출됐을 때와 같은 결과가 보장되는 것을 말합니다.

Context Switching

여러 process나 여러 thread를 돌아가면서 작업을 처리하는 과정을 context switching이라 합니다.

Semaphore & Mutex

여러 process와 thread 작업을 수행 시 자원을 공유하게 되는데 이 때 공유 자원에 접근하는 부분을 Critical Section이라 함. 공유 자원에 대한 접근 처리를 제대로 하지 않으면 많은 문제들을 야기하게 되는데 이를 위해 사용하는 대표적인 방식으로 Semephore, Mutex가 있습니다.

Semaphore

공유 자원에 여러 개의 프로세스가 접근하는 것을 막는 것을 의미하는데 이를 위해 공유 자원의 상태를 나타내는 카운터 변수를 사용합니다. 이 변수는 실제 운영체제, 커널의 값으로 저장되어 각 프로세스가 이를 확인하고 변경할 수 있게 됨. 각 프로세스가 상태값을 보고 사용중인지 사용 가능한 상태인지를 인지해 사용 가능한 때를 기다렸다가 사용하는 방식임. Mutex와는 달리 0, 1 과 같은 이진수 외에 더 큰 숫자를 가지게 할 수 있어 1개의 프로세스만이 자원을 점유하지 않고 공유 자원에 접근할 수 있는 임계치가 되어 접근할 수 있는 process의 개수를 통제할 수 있습니다.

Mutex

상호 배제를 의미하는데 critical section을 가지는 thread들의 실행 시간이 겹치지 않게 해주는 방법을 사용합니다. semaphore와는 달리 1개의 thread만 critical section에 접근할 수 있게 함. Lock, Unlock 개념을 사용하고 Binary Semaphore와 같은 개념으로 쓰임. 하나의 therad가 자원을 사용할 때 Lock을 걸어 다른 thread 접근을 통제하고 작업이 끝나 Unlock하면 다른 thread들이 기다렸다가 접근해 Lock을 하며 동작하게 됩니다.

결론

Mutex는 Binary Semaphore의 일종으로서 가장 큰 차이점은 Mutex는 오직 1개의 thread만 공유 자원에 접근할 수 있고 Semaphore는 지정된 변수 값 만큼 접근할 수 있는 차이점이 있다는 것입니다.

UI Library 제작 및 사용기(react - storybook - tailwind -netlify)

· 9 min read
Alex Han
Software Engineer

Untitled

배경

사내에서 react를 활용한 어드민 페이지 개발이 다수의 서비스에 도입되면서 다수의 어드민 페이지 개발을 통일성 있게 개발해 보자는 니즈가 있었고 이를 위해 공용 모듈을 통해 통일성을 찾아보자는 방법을 연구하기 시작합니다.

프로세스 설계

공통 UI Library 프로세스 설계

공통 UI Library 프로세스 설계

  1. UI Library Developer는 reactjs, storybook, tailwindcss를 이용해 공통으로 사용할 만한 UI 컴포넌트를 제작해 storybook을 통해 이를 테스트해 볼 수 있도록 하는 웹을 Netlify를 통해 배포합니다.
  2. UI Designer와 함께 웹을 보면서 Storybook을 통해 수치를 조정하거나 컴포넌트 변경에 대해 논의 및 수정합니다.
  3. 협의된 컴포넌트를 Npm을 통해 라이브러리로 배포합니다.
  4. Frontend Developer는 Storybook을 통해 컴포넌트의 UI셋팅값에 맞게 UI Designer와 함께 웹 서비스를 개발합니다.
  • 장점
  1. 매 프로젝트별 동일하게 사용하는 컴포넌트 개발을 프로젝트 별로 분리 개발하지 않고 설치해서 바로 사용 가능합니다.
  2. Storybook을 웹에 배포해 UI Designer와 함께 직접 컴포넌트 셋팅값을 조정해 개발적으로도 가능 범주를 조정해 줄 수 있고 UI Designer도 실제 눈앞에서 조정해 볼 수 있어 효율적입니다.
  3. UI 라이브러리를 지속적으로 업그레이드 해 기존 프로젝트 별 일회성 컴포넌트가 아닌 자산화가 가능합니다.
  • 단점
  1. 공용으로 사용하는 모듈의 사용을 강력히 권고하면 프로젝트 별 자유도를 떨어트립니다.
  2. UI 라이브러리의 지속적 업데이트 관리가 필요하며 사용하는 프로젝트에서의 라이브러리 버전 특성을 고려해야 합니다.
  • 주요 고려 사항
  1. 공용으로 사용하는 모듈의 사용은 편의를 제공하기 위함이므로 사용을 권고하기 보다 원하면 사용하도록 하는 것이 좋을 것으로 보입니다.
  2. 공용으로 반드시 사용할 수 있을 만한 컴포넌트 단위를 신중히 선정해야 할 것으로 보입니다.

개발 과정

React Typescript 빌드 설정

  1. React 프로젝트 생성: npm init -y

  2. 필수 라이브러리 설치: npm install --save-dev react react-dom @types/node @types/react @types/react-dom typescript

  3. package.json 설정

    • peerDependencies란 실제로 패키지에서 require, import 하지는 않지만 특정 라이브러리 툴에 호환성을 필요로 할 경우 명시하는 dependencies인데 아래와 같이 react 버전과 react-dom 버전을 명시함으로써 해당 버전에서 사용할 수 있는 라이브러리임을 명시해 오류를 줄이기 위함입니다.
    • filesnpm 라이브러리 배포 시 폴더를 설정하기 위함입니다.
    • scripts는 tsc를 통해 빌드하는 타입스크립트 빌드 build:esm 과 바닐라JS사용 유저를 위해 build:cjs 를 둘 다 빌드하게 합니다.
    {
    "peerDependencies": {
    "react": "^18.1.0",
    "react-dom": "^18.1.0"
    },
    "files": ["dist"],
    "scripts": {
    "build": "rm -rf /dist && npm run build:esm && npm run build:cjs",
    "build:esm": "tsc",
    "build:cjs": "tsc --module CommonJS --outDir dist/cjs"
    }
    }
  4. Typescript 설정

    • 타입스크립트 초기 설정: tsc --init
    • tsconfig.json 설정
    {
    "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "declaration": true,
    "esModuleInterop": true,
    "jsx": "react",
    "lib": ["ES5", "ES2015", "ES2016", "DOM", "ESNext"],
    "types": ["node"],
    "module": "es2015",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noUnusedLocals": true,
    "outDir": "dist/esm",
    "sourceMap": true,
    "strict": true,
    "target": "ES6"
    },
    "include": ["src/**/*.ts", "src/**/*.tsx"]
    }

Storybook 설정

  1. Storybook 초기 설정: npx sb init

  2. 실행 확인: npm run storybook

    Untitled

Npm 배포

  1. 라이브러리로 배포 후 사용하기 위해서는 path 설정을 해줘야 하는데 이를 위해 컴포넌트 별로 export * from “./Button” 이런 식의 경로 설정을 폴더별 index.ts에 적용합니다.
  2. 빌드: npm run build
  3. 배포: npm publish (npm login이 안되어 있다면 이를 선행해야 합니다.)

스크린샷 2022-05-27 오전 10.53.18.png

Netlify Storybook 배포

  1. 위의 storybook 초기 설정을 하고 나면 자동으로 package.json"build-storybook": "build-storybook” script가 생성되는데 이를 이용해 static한 파일들을 storybook-static폴더에 생성할 수 있습니다.(storybook 배포용 폴더)
  2. Netlify에 접속해 해당 깃허브를 연결하고 배포 시 Build Command를 npm run build-storybook 으로 해 storybook-static 폴더를 생성하고 이를 Output Directory로 설정해 깃허브에 푸시하면 storybook 웹싸이트가 자동으로 publish 되도록 자동 배포가 쉽게 설정됩니다.

TailwindCss 적용

  1. 필요 라이브러리 설치 및 초기 설정합니다.
    • 아래를 실행하면 자동으로 tailwind.config.js 가 생성되는데 이 설정파일을 통해 tailwind에서 설정하고 있는 색, 크기, 적용범위, 플러그인 등을 모두 설정할 수 있습니다.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
  1. tailwind를 사용하기 위한 css 파일을 생성합니다.
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. 컴포넌트 라이브러리와 storybook 확인 시 모두 tailwind를 적용하기 위해 src/index.ts.storybook/preview.jstailwind.cssimport 하면 모두 적용됩니다.
  2. 빌드 셋팅

tailwind 공식 홈을 참고해 tailwindcss 파일을 빌드 파일에 추가합니다.

{
"build": "rm -rf /dist && npm run build:esm && npm run build:cjs && npm run build:tailwind",
"build:esm": "tsc",
"build:cjs": "tsc --module CommonJS --outDir dist/cjs",
"build:tailwind": "tailwindcss -i ./src/styles/tailwind.css -o ./dist/cjs/styles/tailwind.css && tailwindcss -i ./src/styles/tailwind.css -o ./dist/esm/styles/tailwind.css"
}

결론

tailwindcss가 적용된 형태로 storybook 웹 싸이트가 구현된 모습

tailwindcss가 적용된 형태로 storybook 웹 싸이트가 구현된 모습입니다.

npm 패키지 배포된 모습

npm 패키지 배포된 모습입니다.

배포한 라이브러리 설치한 코드

배포한 라이브러리 설치한 코드입니다.

설치한 라이브러리의 버튼을 가져다 사용한 코드

설치한 라이브러리의 버튼을 가져다 사용한 코드입니다.

UI Library를 직접 설치해 웹에서 사용한 모습

UI Library를 직접 설치해 웹에서 사용한 모습입니다.

***참고사항

  1. 라이브러리를 설치는 정상적으로 되고 실행도 정상적으로 되지만 sourcemap 오류가 발생하는데 이를 해결하기 위해 .env 파일에 GENERATE_SOURCEMAP=false 를 작성해 저장하면 실행 시 오류 메시지가 보이지 않습니다.
  2. tailwindcss 설정 순서에 따라 적용이 잘 안 될 수 있어 순서대로 설정해야 합니다.
  3. 소스: https://github.com/hanhyeonkyu/react-ui-lib-ts-test

참고 문서

UI라이브러리 제작: https://javascript.plainenglish.io/build-a-react-component-library-using-typescript-storybook-86d3562aa53a

Storybook - Tailwind Netlify: https://theodorusclarence.com/blog/nextjs-storybook-tailwind

TailwindCss 공홈: https://tailwindcss.com/docs/guides/create-react-app

Deploy Flutter Web App With Netlify(Flutter Web App을 Netlify로 배포)

· 5 min read
Alex Han
Software Engineer

배경

그 동안 react, nextjs, hugo ... 등을 활용해 프론트엔드 개발을 진행하고 간단히 주변에 공유할 때 Netlify 를 자주 활용해 왔습니다. 하지만 요새 공부 중인 Flutter 는 웹도 앱도 모두 한번에 개발할 수 있는 신무기지만 Netlify 를 활용해 배포해 본 적이 없어 간단한 작업이지만 블로그에 남겨봅니다.

소개

Netlify는 웹 애플리케이션 및 정적 웹사이트를 위한 호스팅 및 서버리스 백엔드 서비스를 제공하는데 저의 경우 간단하게 프론트엔드를 배포할 때만 사용하고 있습니다.

Flutter는 모바일 앱과 웹 앱, 데스크톱 앱을 단일 코드 베이스로 개발할 수 있도록 Google이 개발한 프레임워크인데 Google 답게 Docs가 아주 잘 되어 있고 공부할 수 있도록 많은 지원을 해주고 있고 Flutter 자체가 가진 기능들이 많기 때문에 react-native 개발 때 느꼈던 dependency 지옥으로부터 조금이나마 해방된 느낌입니다.

팀원들에게 Flutter를 이용해 웹, 앱 모두 간단하게 개발된다는 것을 보여주기 위해 Netlify에 로그인 해 배포하려 했는데 이것을 몰라 찾아 봤고 금방 해답을 찾을 수 있었습니다.

수동 배포

우선 기본 전제는 Flutter 설치를 완전히 했다는 전제로 시작합니다.(공식문서 참고)

  1. 우선 Flutter 프로젝트를 생성합니다. flutter create [project_name]

  2. 프로젝트 폴더로 들어가서 flutter build web을 실행하면 build/web 폴더가 생성되고 배포용 js 빌드 파일들이 생성됩니다.(이것이 프론트엔드 배포용 소스)

  3. 아래와 같이 Netlify에 접속에 Deploy manually 를 클릭하면

    deploymanual

  4. 2번에서 생성한 배포용 폴더를 아래와 같이 Drag & Drop 하면 배포 성공!

    dragdrop

아주 간단해서 정리할 것도 많지 않았지만 그래도 정리해 두면 다음번에 생각을 덜해도 되니까 적어둬야지 😎

자동 배포

위에서 봤던 수동 배포로 해도 과정이 간단하고 실수로 자동으로 배포되는 것을 원치 않기 때문에 수동 배포를 좀 더 원하지만 자동 배포에 대해서도 알게 되어 이 또한 정리해 봅니다.

  1. 수동 배포의 1번을 완료하고 아래와 같이 Netlify에 접속 후 import an existing project 를 클릭합니다.

importexist

  1. 본인의 git repository를 선택하고 해당 레파지토리를 netlify에 공유할 수 있도록 설정합니다.

importexist2

  1. 이제 Deploy & Build 셋팅을 설정하는데, 명령과 셋팅은 아래와 같습니다.
  • Build command

아래와 같이 만약 flutter 폴더가 있으면 git pull을 통해 최신화 하고 없다면 git clone 을 통해 flutter를 가져오고 flutter 폴더에 있는 실행파일을 활용해 프로젝트에 웹을 적용하고 웹을 빌드해 배포합니다.

if cd flutter; then git pull && cd ..; else git clone https://github.com/flutter/flutter.git; fi && flutter/bin/flutter config --enable-web && flutter/bin/flutter build web --release
  • Publish directory

배포 폴더를 build/web으로 하여 Build command에서 생성한 배포 폴더로 설정합니다.

build/web

이렇게 설정하면 git에 푸시할 때마다 build command가 실행되고 설정된 publish directory설정에 따라 배포하게 되면서 자동 배포가 완성됩니다.

아직 공식 홈페이지에서 Deploy 셋팅 방법에 대해 Flutter는 다루고 있지 않지만 위와 같은 방식으로 처리가 가능합니다.

도움이 되는 포스팅이기를 빕니다. 🧑🏻‍💻

React Global State(리엑트 글로벌 상태 관리)

· 3 min read
Alex Han
Software Engineer

회사에서 동료들과 이야기하던 중 우리 회사도 redux를 도입해야 하는 거 아니냐는 말을 들었습니다. 그렇습니다. 사실 redux를 도입하던 global state 관리할 만한 뭔가를 진작 도입했어야 했습니다. 우리는 왜 여태 컴포넌트 간의 props 를 넘겨가며 코드를 계속 복잡하게 만들고 있어야 했나...

배경

사실 시작은 이렇다. 처음 개발한 개발자 분이 react 기본 router, state 관리 방식까지 모두 자기만의 스타일로 만들어 그 패턴으로 개발했습니다. 황당했지만 굴러드러온 돌이고 바빴기 때문에 이를 고칠 시간적 여유도 없었습니다.

그렇게 시간이 흘러 여러 차례 마이그레이션, 신규 런칭 과정을 거쳤지만 이번에도 같은 실수를 반복하며 제대로 개선되지 못하고 본인만의 패턴들을 꽉 체워 두었습니다. 결국 모두가 새로 나가고 한번 더 새로운 사람들로 체워지면서 현재의 새로운 프론트엔드, 앱 개발자들은 이 상황을 맞닥뜨려야 했습니다.

바쁜 일상

프론트엔드, 앱, 서비스 백엔드 개발자 분들은 계속된 요구사항과 유지보수 개발에 지쳐있었습니다. 문제를 이야기한지 시간이 많이 지났지만 그 누구도 기술적 검토나 토론을 이어가지 않았습니다.

그래서 자의로 스터디도 할 겸, 사내 문서도 만들 겸, 테스트해 react의 global state 관리 방식에 대해 조사해보기로 했습니다.

위의 링크에 있는 비교 분석을 참고해 실제 적용하며 테스트했고 recoil 로 적용하자고 결론에 다다랐습니다.