Node.js기반 통합 시스템 개발 - 문제개선

임기호·2025년 4월 9일

팀원들과 문제점을 논의한 결과, 개선이 필요하다고 판단한 세 가지 주요 영역이 도출되었습니다. 이 개선 사항들은 개발 환경의 효율성을 높이고 유지보수성을 강화하며, 시스템 통합의 전반적인 품질을 향상시키기 위한 핵심 조치로 결정되었습니다.


1. 운영 서버 DB 처리 속도 개선

  • 테스트 서버에서는 문제 없이 작동하였으나, 운영 서버에서 DB 데이터를 가져오는 속도가 미묘하게 느린 문제가 있었습니다.
  • 이에, DB Pool을 도입하여 DB Connection 을 재활용하여 미묘하게 느린 문제를 해결하였습니다.

2. API-DB 프로시저 형상 관리 개선

  • API - DB 프로시저를 구조화하여 운영에 편리성을 확보하였지만, 프로시저 변경이 쉬운 특성상, 실수로 인한 오류 발생 가능성이 높고, 한 번 문제가 발생하면 이를 복구하기 어려워지는 가능성이 있었습니다.
  • 이 문제에 대응하여 이벤트 트리거를 도입, 프로시저 변경 시 모든 변경 히스토리가 자동으로 기록되도록 하였습니다. 실수나 오류 발생 시 빠른 복구 작업을 진행할 수 있는 기반을 마련하였습니다.
CREATE OR REPLACE FUNCTION admin.f_proc_event_hist()
 RETURNS event_trigger
 LANGUAGE plpgsql
AS $function$
declare
	v_ip	text;
begin

	v_ip := coalesce(inet_client_addr()::text, '::1');

	if v_ip like 'x.x.x.x%' then
		return;
	end if;	

	insert into admin.t_proc_history
	select now()
		, coalesce(nspname, t.trigger_schema)
		, coalesce(proname, t.trigger_name)
		, command_tag::char(1)
		, coalesce(pg_get_functiondef(p.oid)
		, 'create trigger ' || trigger_name || ' ' || action_timing || ' ' || event_manipulation
        	|| E'\non\n' || quote_ident(event_object_schema) || '.' || quote_ident(event_object_table) || ' for ' || action_orientation 
		|| coalesce(E'\rwhen (' || action_condition || E')\r', E'\r')  || action_statement || ';')
		, coalesce(v_ip, '::1')
	 from pg_event_trigger_ddl_commands() e
	 left join pg_proc p on p.oid = e.objid
	 left join pg_namespace n on n.oid = pronamespace
	 left join information_schema.triggers t on object_identity like t.trigger_name || '%';
end
$function$
;

상태 관리 재구성

기존에는 Reducer와 Context API를 활용하여 상태를 관리하였습니다. 초기에는 단순한 상태 객체 덕분에 관리가 편리했지만, 점차 관리해야 할 상태 항목이 많아짐에 따라 다음과 같은 문제가 발생하였습니다:

  • 상태의 복잡성 증가:
    예를 들어, 상태 객체에는 searchParams, 여러 개의 boolean 값, 선택된 행과 탭 정보, 다양한 팝업 상태, 그리드 상태 등이 모두 하나의 객체에 포함되어 있습니다. 이로 인해 특정 상태의 변경 여부를 확인하거나 업데이트하는 데 시간이 많이 소요되었습니다.

  • TypeScript 장점 활용의 한계:
    한 곳에 모든 상태를 몰아놓은 구조로 인해, TypeScript의 강력한 타입 체계, 즉 빌드 단계에서 잠재적인 오류를 충분히 포착하지 못하는 문제로 이어집니다.

문제의 코드

const [state, dispatch] = useReducer(reducer, {
    objState: {
      searchParams: {},
      isMSearch: false,
      isMDSearch: false,
      isCGOSearch: false,   //Cargo
      isCSTSearch : false,  //Cost
      isRefresh : false,
      mSelectedRow: {},
      mSelectedCargo: {},
      tab1: [{ cd: "Main", cd_nm: "Main" }],
      MselectedTab: "Main",
      isFirstRender: true,    //화면 처음 렌더링시에만 조회버튼 클릭 되도록 하기위한 state
      trans_mode: "",
      trans_type: "",
      gridRef_m: useRef<any | null>(null),
      isShpContPopUpOpen: false,
      isCarrierContPopupOpen: false,
      isCYPopupOpen: false,
      isCYContPopupOpen:false,
      isPickupPopupOpen: false,
      isMailRcvPopupOpen:false,
      isMailSendPopupOpen : false, //send_mail
      isWaybillPopupOpen:false,
      mGridState: {},
      mGridStateInit: null,
      isSaveMain: false,
      isSaveDetail: false

    }

이러한 문제점을 해결하기 위해, 기능별로 상태 관리를 분리하고 보다 명확한 타입 정의를 적용하는 방식을 적용했습니다. 팀원들과 Redux, Zustand 등 다양한 상태 관리 도구를 검토하며, 효율적인 관리 체계를 고민했고 상대적으로 간단하고 직관적인 Zustand가 적당하다고 판단했습니다.

interface StoreState {
    searchParams: Record<string, any>;
    loadDatas: gridData[] | null;
    mainDatas: gridData | null;
    mainSelectedRow: Record<string, any> | null;
    updatedDatas: Record<string, any>;
  	popUp: Record<string, boolean>
    isInputPanelCollapse: boolean;
    gridRef: {
        refFHList: any
    }    
}

interface StoreActions {
    getLoad : () => Promise<any>;
    getFHDatas: (params: any) => Promise<any> | undefined;
    setFHDatas: () => Promise<any>;
    setCloseInvoice: (param:any) => Promise<any>;
    resetSearchParam: () => void;
    setState: (newState: Partial<StoreState>) => void;
}

type Store = StoreState & {
    actions : StoreActions;
}

const initValue: StoreState = {
    cust_code: null,
    cust_mode: null,
    searchParams: {
        fr_date: dayjs().subtract(0, "days").startOf("days").format("YYYYMMDD"),
        to_date: dayjs().subtract(0, "days").startOf("days").format("YYYYMMDD"),
        no: '', // HWB, 
        cust_code : '', //cnee id
        s_settlement_user:  'ALL', //조회용 settlement_user
        logis_id:  'ALL',
        broker_id:  'ALL',
        dtd_fh:  'ALL',
    },
    loadDatas:null,
    mainDatas: null,
    mainSelectedRow: null,
    updatedDatas: {},
    isInputPanelCollapse: true,
    gridRef: {
        refFHList: createRef()
    },
    popUp: {
      isShpContPopUpOpen: false,
      isCarrierContPopupOpen: false,
      isCYPopupOpen: false,
      isCYContPopupOpen:false,
      isPickupPopupOpen: false,
      isMailRcvPopupOpen:false,
      isMailSendPopupOpen : false, //send_mail
      isWaybillPopupOpen:false,
    }
}

장점

  1. 타입 안전성 향상:
    모든 상태와 동작이 명시적으로 타입화되어 있어, 컴파일 단계에서 잘못된 타입 사용이나 예기치 않은 오류를 사전에 방지할 수 있습니다.

  2. 코드 가독성 및 유지보수성 개선:
    상태(StoreState)와 동작(StoreActions)을 분리하여 정의함으로써, 각 구성 요소의 역할이 명확해지고, 코드 리뷰 및 협업 시 이해하기 쉬워집니다.

  3. 효율적인 협업:
    명확한 인터페이스 정의를 통해 팀원 간의 소통이 원활해지고, 각자의 역할에 따른 적절한 변경을 가할 수 있어 개발 속도가 향상됩니다.

이처럼, TypeScript의 타입 시스템을 적극 활용한 상태 관리 구조의 개선은 코드 품질과 개발 생산성을 크게 향상시키는 결과를 가져왔습니다.


아직 해결해야할 문제가 많지만 앞으로도 지속적인 검토와 개선을 통해 더욱 안정적이고 확장 가능한 시스템 구축을 목표로 하고 있습니다.

감사합니다.

0개의 댓글