모듈 : 소프트웨어 설계에서 기능단위로 분해하고 추상화 되어 재사용 및 공유 가능한 수준으로 만들어진 단위
모듈화 : 소프트웨어의 성능을 향상시키거나 시스템의 디버깅, 시험, 통합 및 수정을 용이하도록 하는 소프트웨어 설계 기법
추상화
멘토링 시간 때 멘토님께서 모듈화, 추상화 개념에 대해서 설명해주셨다.
설명을 듣고 내가 진행했던 프로젝트에서 어떤 로직을 고칠 수 있을까 고민했는데 마침 딱 고치기 좋은 로직이 있었다.
export async function signUp(data: object) {
try {
const response = await client.post("/users/", data);
const result = response.data;
return result;
} catch (e: any) {
console.log(e);
const { method, url } = e.config; // axios의 error객체
const { status } = e.response;
Sentry.withScope((scope) => {
scope.setTag("api", "signUp"); // 태그 설정
scope.setLevel("warning"); // 레벨 설정
scope.setFingerprint([method, status, url]);
Sentry.captureException(new Error(e.response.data.message));
});
return e.response.data.message;
}
}
export async function logIn(data: object) {
try {
const response = await client.post("/auth/login", data);
const result = response.data;
return result;
} catch (e: any) {
console.log(e);
const { method, url } = e.config; // axios의 error객체
const { status } = e.response;
Sentry.withScope((scope) => {
scope.setTag("api", "login"); // 태그 설정
scope.setLevel("warning"); // 레벨 설정
scope.setFingerprint([method, status, url]);
scope.captureException(new Error(e.response.data.message));
});
return e.response.data.message;
}
}
로그인, 회원가입을 기능을 수행할 때 사용되는 API로직을 예시로 들고 왔습니다. 다른 로직들도 이 코드와 거의 똑같이 구현되어 있다고 생각하시면 됩니다. 그렇게 되면 아래와 같은 문제점이 생깁니다.
앞서말한 문제점말고도 정말 많은 문제가 발생할 수 있다고 생각이 듭니다. 그래서 리펙토링을 진행해보려고 합니다.
import client from "./axios";
import * as Sentry from "@sentry/react";
export const baseHttpClient = () => {
// T: response data type
// P: API 통신시 필요한 Body data type
async function get<T, P>(url: string, params?: P) {
try {
const response = await client.get<T>(url, { params });
return response.data;
} catch (e) {
const { method, url } = e.config; // axios의 error객체
const { status } = e.response;
Sentry.withScope((scope) => {
scope.setTag("api", "get"); // 태그 설정
scope.setLevel("warning"); // 레벨 설정
scope.setFingerprint([method, status, url]);
Sentry.captureException(new Error(e.response.data.message));
});
return e.response.data.message;
}
}
async function post<T, P>(url: string, data: P, headers?: string) {
try {
const response = await client.post<T>(url, data, {
headers: {
"Content-Type": headers,
},
});
return response.data;
} catch (e) {
const { method, url } = e.config; // axios의 error객체
const { status } = e.response;
Sentry.withScope((scope) => {
scope.setTag("api", "post"); // 태그 설정
scope.setLevel("warning"); // 레벨 설정
scope.setFingerprint([method, status, url]);
Sentry.captureException(new Error(e.response.data.message));
});
return e.response.data.message;
}
}
{...}
return {
get,
post,
put,
delete: del,
};
};
export default baseHttpClient;
GET, POST, PUT, DELETE 메서드 기준으로 추상화를 진행해 보았습니다.
API 통신을 할 때 필요한 URL, Params, BodyData등 통신에 필요한 요소들은 외부에서 주입할 수 있도록 바꿨습니다.
그리고 로직에서 내보내주는 Response 타입은 제네릭를 사용해 외부에서 타입을 지정해줄 수 있도록 하였습니다.
const httpClient = baseHttpClient();
export async function signUp(data: SignUpRequestbody) {
const response = await httpClient.post<UserInfoResponse, SignUpRequestbody>(
"/users/",
data
);
return response;
}
export async function logIn(data: LoginRequestbody) {
const response = await httpClient.post<UserLoginResponse, LoginRequestbody>(
"/auth/login",
data
);
return response;
}
위와같이 추가한 HttpClient 로직을 사용해서 아까 처음에 봤던 로그인, 회원가입 리펙토링 시켜주었습니다.
확실히 코드의 양이 줄었고 앞으로 생길 변경 사항에 좀 더 유연하게 대응할 수 있을 거 같습니다.
그리고 만약 문제가 생기면 HttpClient 로직을 들여다 보면서 문제를 할 수 있기 때문에 유지보수도 더욱 편하게 진행할 수 있을 거 같습니다.
Sentry 로직 또한 반복되고 있기 때문에 바꿔볼 수 있을 거 같은데 이건 나중에 해보겠습니다. 🧐
📌 Taskify 깃허브 링크
https://github.com/taskify-team7/taskify