안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

회사 프로젝트 작업 일지

[오픈갤러리] Django 서버 정리 작업 ⑦ - 사용자 로그인/회원가입 및 관리자 권한 제어 방식 정리 및 개선

피그브라더 2021. 6. 1. 15:26

목차
⦁ 통일성을 갖추고, 읽을 수 없는 코드는 읽을 수 있게
⦁ django-allauth 패키지의 구현 원리 파악
⦁ 이메일 발송 코드 리팩토링
⦁ 이메일 로그인/회원가입 관련 코드 정리 및 개선
⦁ 소셜 로그인/회원가입 관련 코드 정리 및 개선
⦁ 회원 정보 관련 각종 페이지 정리 및 개선
⦁ 불필요한 정적(Static) 파일 제거
⦁ 관리자 정보 조회 및 수정 기능 개선

 

처음 배울 때는 기본인 것처럼 배우지만 실제로 처음 회사에 들어가고 나면 1년이 넘도록 잘 알고 있지 못하는 경우가 많을 정도로 생각보다 꽤나 까다로운 부분이 있다. 그것은 바로 로그인, 회원가입과 관련한 인증 부분이다. 인증 관련 코드는 서비스에서 한 번 구축되고 나면 이후에 크게 고치거나 할 일이 별로 없기 때문에, 잘 이해하지 못하고 있거나 이해했어도 까먹어 버리기 쉬운 것이다. 필자의 경우도 그러하였다. 그렇지만 이번 정리 작업을 통해 인증 관련 부분에 대한 이해도도 깊어지고, 오픈갤러리의 인증 시스템도 굉장히 깔끔해졌다. 본 포스팅에서는 그 정리 작업을 어떠한 맥락에서 어떠한 방식으로 진행하였는지에 대해 다뤄보도록 하겠다.

 


▎통일성을 갖추고, 읽을 수 없는 코드는 읽을 수 있게

오픈갤러리의 경우 이메일을 이용한 로그인/회원가입소셜 계정을 이용한 로그인/회원가입을 모두 지원한다. 이메일 로그인/회원가입은 대부분 자체 구현되어 있었고, 소셜 로그인/회원가입은 django-allauth 패키지와 SDK에 의해 구현되어 있었다. 그러나 오픈갤러리가 지원하는 카카오, 페이스북, 네이버 로그인/회원가입은 django-allauth가 기본적으로 지원함에도 불구하고 카카오를 제외한 나머지는 SDK에 의해서만 구현되어 있었다. 결국 django-allauth는 오로지 카카오만을 위해 사용되었다는 것이다. 필자는 이것을 문제 상황으로 인식했다.

 

만약 카카오만 SDK를 쓰지 않고 django-allauth를 써야 했던 논리적인 이유가 있다면 납득이 될 것이다. 하지만 근속이 오래되신 개발자분께 여쭤 봐도 그런 이유는 전혀 없었고, 카카오는 카카오대로, 페이스북은 페이스북대로, 네이버는 네이버대로 따로 생각하다가 그 통일성이 깨져버린 듯했다. 아마 카카오는 django-allauth로 깔끔하게 구현을 했는데, 네이버와 페이스북을 위한 기존의 SDK 코드들은 전환 비용 문제 때문에 django-allauth로 옮기지 않고 그대로 내버려 둔 듯했다. 결국, 필자는 이왕 django-allauth를 쓰는 김에 통일성 있게 페이스북과 네이버도 SDK가 아닌 django-allauth로 구현해야겠다고 결론을 내렸다. (필자는 이런 총대를 메는 것을 좋아하는 취향인가 보다.)

 

단순히 SDK가 별로여서 그런 것은 아니었다. 이미 말했듯 통일성의 문제도 있었고, 무엇보다 코드의 가독성이 처참했다. SDK 관련 JavaScript 코드들이 여러 곳에 규칙 없이 분산되어 있던 것은 물론, 코드에 주석이 전혀 달려 있지 않아서 무엇이 필요한 코드인지, 이 코드의 역할은 무엇인지 파악하는 것조차 굉장히 어려웠다. 이러한 문제는 필자로 하여금 정리 욕구를 더욱 참을 수 없게 만들었다. "django-allauth로 하면 훨씬 깔끔할 텐데 왜..."라는 생각이 머리에서 떠나가질 않았던 것 같다.

또한 로그인/회원가입 코드를 정리해야 하는 이유가 하나 더 있었다. 당시 오픈갤러리가 외주사를 통해 하이브리드 앱을 개발 중이었는데, 하이브리드 앱에 마케팅 코드를 심으려면 로그인/회원가입 관련 부분이 반드시 정리되어 있어야만 했다. 이를 목적으로 진행한 정리 작업은 아니었는데, 우연히 이유가 하나 더 생겨서 동기부여가 더 잘 됐던 것 같다.

 


django-allauth 패키지의 구현 원리 파악

 

Welcome to django-allauth! — django-allauth 0.43.0 documentation

© Copyright 2017, Raymond Penners Revision 9f52b43b.

django-allauth.readthedocs.io

페이스북과 네이버의 로그인/회원가입을 django-allauth로 바꾸기에 앞서, django-allauth의 기본적인 구현 원리를 먼저 파악해보기로 하였다. 그래야 소셜 로그인/회원가입 관련 코드에서 능숙하게 커스터마이징이 가능할 것이고, OAuth 2.0에 대한 이해도도 더 깊어질 것으로 판단했기 때문이다. 결과적으로 이해한 django-allauth의 구현 원리를 여기서 자세히 언급하지는 않겠다. 대신, 다른 포스팅에서 이를 다뤘으니 아래에 링크를 남기도록 하겠다.

 

▼ django-allauth 구현 원리 관련 포스팅

 

[Django] django-allauth 소셜 로그인 구현 원리 (OAuth 2.0 기반)

Django의 라이브러리 중 하나인 django-allauth는 여러 종류의 소셜 로그인을 구현해두었다. 카카오 로그인, 페이스북 로그인, 구글 로그인, 네이버 로그인 등 아주 많은 종류의 소셜 서비스와 편리하

it-eldorado.tistory.com

 


이메일 발송 코드 리팩토링

정리를 위해 로그인/회원가입 관련 코드들을 전부 읽으며 파악하던 도중, 이메일 발송 관련 코드를 미리 정리해두면 좋겠다는 생각을 하게 되었다. 회원가입을 할 때 혹은 이메일 인증을 할 때 이메일 발송 코드가 필요한데, 이메일 발송 코드의 리팩토링이 적절히 되어 있지 않았기 때문이다.

 

이를 위해, send_email() 함수와 send_template_email() 함수를 별도로 정의해두고 필요할 때 가져다 쓰도록 하였다. send_email() 함수는 단순 텍스트를 이메일로 발송하는 함수이고, send_template_email() 함수는 템플릿 파일로 렌더링 되는 내용을 이메일로 발송하는 함수이다. (자세한 코드는 생략한다.)

 

또한 이메일 발송 관련 Django 설정도 수정하였다. 원래 테스트 환경(로컬 혹은 테스트 서버)에서는 실제로 이메일이 발송되지 않고 콘솔에만 이메일의 내용이 출력되는 방식이었는데, 환경 변수로 이메일 호스트의 계정 정보만 설정해주면 테스트 환경에서도 이메일이 발송되도록 수정하였다. 단, 테스트 환경에서는 반드시 개발자 계정으로만 이메일이 발송되도록 함수 내부에서 추상화하였다. 설정 코드는 다음과 같다.

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = '이메일 호스트 엔드 포인트'
EMAIL_PORT = 587
EMAIL_HOST_USER = env('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD', default='')
EMAIL_USE_TLS = True

 


▎이메일 로그인/회원가입 관련 코드 정리 및 개선

이왕 로그인/회원가입 코드를 정리하는 김에, 소셜 로그인/회원가입뿐 아니라 이메일 로그인/회원가입도 함께 정리하기로 하였다. 문제 상황만 보면 소셜 로그인/회원가입만 정리해도 되지만, 서버 정리 작업이라는 큰 틀에서 보면 인증 방식과 관련된 부분은 전부 정리하는 게 좋다고 판단하였기 때문이다. 그런데 그 전에, 로그인 페이지의 구성과 관련하여 진행한 정리 작업에 대해 먼저 얘기해보도록 하겠다. 즉, 이는 로그인/회원가입 공통 내용이다.

 

먼저, 기존에 모달로 존재하던 로그인 창을 삭제하고 로그인 페이지로 통합시켰다. React와 같은 프레임워크를 사용하는 것이 아니어서 모달로 개발된 로그인 창은 코드가 상당히 복잡했을 뿐 아니라, 별도의 로그인 페이지도 따로 있다 보니 둘을 항상 함께 관리해줘야 하는 유지보수 측면의 비효율이 존재했기 때문이다. 따라서 모달로 개발된 로그인 창을 삭제하고, 로그인 페이지만을 사용하는 것으로 결론 내렸다. 그리고 이렇게 하는 것이 마케팅의 측면에서도 페이지뷰 이벤트를 잡을 수 있기 때문에 더욱 이득이라 생각하였다.

 

단, (작품 주문 등의) 로그인이 필요한 기능을 사용할 때 로그인 페이지로 이동하게 되면 로그인 후에는 원래의 페이지로 돌아오도록 해야 했다. 그래야 원래 모달을 쓸 때와 동작이 같아지기 때문이다. 따라서 로그인 페이지로 이동할 때는 쿼리 스트링에 돌아올 URL 정보를 넘겨주도록 구현하였다.

 

이처럼 공통 내용을 먼저 정리하고 나서야 이메일 로그인/회원가입 관련 정리 작업을 시작하였다. 이때 가장 중요시했던 부분은 다음과 같다. 그것은 바로, 이메일 로그인/회원가입 관련 코드는 오로지 자체적인 구현에만 의존하도록 하는 것이었다. 즉, django-allauth는 오로지 소셜 로그인/회원가입 관련 코드에만 사용하도록 하는 것이었다. 물론 django-allauth는 일반적인 로그인/회원가입까지 구현할 수 있도록 기능을 제공한다. 그러나 django-allauth가 제공하는 그 기능들을 커스터마이징 하는 것보다 자체적으로 구현하는 게 훨씬 더 수월하다는 판단을 내렸다. 또한, 애초에 django-allauth를 도입한 이유도 소셜 로그인/회원가입을 위함이었지 일반적인 로그인/회원가입을 위함이 아니었다.

 

이런 상황에서 이메일 로그인/회원가입 관련 코드에 어중간하게 django-allauth 기능을 개입시키는 것보다는 깔끔하게 전부 빼는 게 맞다고 생각했다. 다시 말해서, 이메일 로그인/회원가입은 자체 구현, 그리고 소셜 로그인/회원가입은 django-allauth에 의한 구현으로 명확하게 구분을 하여 추후 개발 및 유지보수의 효율을 증진시킨 것이다. 기존의 이메일 로그인/회원가입 관련 코드에는 django-allauth의 기능이 애매하게 첨가되어 있었다.

자주 언급해서 눈치를 챈 사람도 있겠지만, 서버 정리 작업이라는 큰 틀 안에서 결국 우리의 최종 목표는 개발 및 유지보수의 효율성을 최대로 높이는 방향으로 코드를 깔끔하게 정리하고 개선하는 것이었다. 이 작업도 예외는 아니었기 때문에, 어떻게 하는 것이 개발 및 유지보수의 효율성을 높일 수 있는 방향인지 끊임없이 고민해야 했다. 그 고민의 결과가 바로 이것이다.

 

구현의 경우에는 기본적으로 django.contrib.auth 앱에서 제공하는 authenticate(), login(), logout() 함수를 활용하였다. 즉, 세션 기반의 인증 방식을 채택하였다. django-allauth를 전혀 활용하지 않았기 때문에, 모든 구현은 뷰 안에서 이뤄졌다. 그리고 이 작업은 개선이라기보다는 재개발이라고 보는 것이 맞을 것 같다. 기존의 구현은 참고만 하고 사실상 새로 구현하였기 때문이다. (자세한 코드는 공개하기 어려운 점 이해 바란다.)

 


▎소셜 로그인/회원가입 관련 코드 정리 및 개선

이메일 로그인/회원가입 관련 코드를 정리한 후, 본격적으로 소셜 로그인/회원가입 관련 코드를 정리하였다. 여기서도 마찬가지로 어떻게 정리해야 개발 및 유지보수의 효율이 최대화될지 끊임없이 고민했다. 왜냐하면 오버라이딩을 어느 레벨에서 하느냐에 따라 코드의 구조가 달라지기 때문이다. 고민의 결과는 다음과 같다.

소셜 로그인/회원가입 관련 코드는 반드시 CustomSocialAccountAdapter에만 작성하도록 한다. 이는 소셜 로그인/회원가입 관련 코드를 단일 원천(Single Source)에서 관리하기 위함이다.

 

CustomSocialAccountAdapter는 SocialAccountAdapter를 상속하여 정의한 어댑터로, 소셜 로그인/회원가입 과정의 일부를 오버라이딩할 수 있는 몇몇 메소드들을 제공하고 있다. 본 작업의 구현에서 핵심적인 역할을 한 메소드들은 다음과 같이 두 가지이다. (자세한 내용은 이곳을 참고)

populate_user() 메소드 : 소셜 회원가입 시 생성할 유저 객체의 필드 값들을 채워주는 메소드이다. 이 메소드를 오버라이딩 하면 API를 통해 읽어온 소셜 계정의 정보를 유저 객체의 필드에 어떻게 채워줄지 정해줄 수 있다.

pre_social_login() 메소드 : 기본적으로는 비어 있는 오버라이딩 전용 메소드로, 소셜 로그인/회원가입 직전에 호출되는 메소드이다. 이 메소드를 오버라이딩 하면 소셜 로그인/회원가입 과정의 일부를 커스터마이징 할 수 있다. 예를 들어, 소셜 로그인 시 휴면 계정이라면 휴면 계정 복구 페이지로 리다이렉트를 시킨다든지, 사용자가 소셜 회원가입 시 이메일 제공에 동의를 하지 않으면 발급된 액세스 토큰을 이용하여 연결 끊기 API를 호출한 뒤 회원가입 과정을 취소시킨다든지 등이 있다.

 

기본적으로 소셜 로그인/회원가입 관련 코드는 위 두 개의 메소드를 구현하는 것이 전부라고 보아도 무방하다. 그만큼 다양한 로직을 위 두 개의 메소드 내부에 구현하였다는 것이다. 마찬가지로 자세한 코드는 보안 문제로 보여줄 수 없지만, 구현 시 중요하게 생각한 부분을 간략하게 정리해보면 다음과 같다.

 

1. 인증 토큰을 이용하여 가져오는 소셜 계정 정보의 처리 (feat. 정보 제공 동의)

OAuth 2.0의 기본 원리에 부합하게, django-allauth는 소셜 회원가입 시 사용자가 정보 제공에 동의를 함으로써 발급되는 인증 토큰을 이용하여 해당 소셜 계정의 정보를 가져오는 API를 호출한다. 그런데 소셜의 종류마다 사용자가 제공에 동의를 하는 정보의 종류가 다르기 때문에 이에 대한 통일이 필요하였다.

 

따라서 카카오, 페이스북, 네이버에서 사용자가 제공해주기를 바라는 정보의 풀을 통일시켰다. 가입에 필수적인 이름과 이메일은 필수 항목으로 두었고, 나머지는 선택 항목으로 두었다. 그리고 populate_user() 메소드에서도 소셜의 종류에 따라 분기 처리를 하여(동일한 정보여도 소셜의 종류마다 API 응답의 형태가 다르기 때문) 사용자가 제공한 정보를 적절히 읽어온 뒤 이를 유저 객체의 필드에 저장해주도록 구현하였다.

 

2. 연결 끊기 API 리팩토링

다만 필자가 초반에 놓친 부분이 하나 있었는데, 필수 항목이면 사용자가 반드시 체크를 할 것으로 보장되는 줄 알았다는 것이다. 실제로 카카오는 필수 항목인 경우에는 체크를 하지 않으면 다음 단계로 넘어가지 못하도록 해두었다. 그러나 네이버 같은 경우 필수 항목이어도 사용자가 원하면 체크를 해제하고 다음 단계로 넘어갈 수 있었다. 그래서 서버 단에서 이러한 경우에 대한 예외 처리를 해줘야만 했다. 이는 pre_social_login() 메소드에서 구현하였다.

 

그런데 더 중요한 문제가 있었다. 그것은 바로, 사용자가 필수 항목을 체크하지 않고 제출한 내용도 소셜 서버에 저장이 되기 때문에 다음에 다시 소셜 회원가입을 시도하더라도 그것이 재사용된다는 것이었다. 따라서 한 번 필수 항목을 체크하지 않고 제출했다면 회원가입 시도는 계속 실패하게 된다.

 

이 문제를 해결하려면 사용자가 필수 항목을 체크하지 않고 제출했을 때 서버 단에서 적절히 연결 끊기 API를 호출하여 소셜 서버에 저장된 내용을 삭제해줘야 했다. 그렇지 않으면 사용자가 직접 연결 끊기를 진행해야 하는데, 이건 당연히 바람직하지 못했다. 따라서 세 종류의 소셜 각각에 대하여 연결 끊기 API를 검토한 다음, 소셜의 종류와 액세스 토큰(필요한 경우 소셜 계정의 ID까지)만 넘겨주면 편리하게 연결 끊기를 할 수 있도록 disconnect() 함수를 별도로 정의해두었다. 그리고 사용자가 필수 항목을 체크하지 않으면 서버 단에서 이 함수를 호출하도록 하였다.

▎카카오 연결 끊기 API
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#unlink

▎페이스북 연결 끊기 API
https://developers.facebook.com/docs/facebook-login/permissions/requesting-and-revoking?locale=ko_KR#revokelogin

▎네이버 연결 끊기 API
https://developers.naver.com/docs/login/devguide/devguide.md#5-3-%EB%84%A4%EC%95%84%EB%A1%9C-%EC%97%B0%EB%8F%99-%ED%95%B4%EC%A0%9C

 

간단히 생각해서, disconnect() 함수는 한 번 제출한 정보 제공 동의 내역이 그대로 다시 사용되지 않도록 하기 위한, 다시 말해서 조금이라도 실패하면 모든 것을 되돌리기 위한 Atomic 정책의 일환으로 기능하였다. 이로써 소셜 회원가입이 실패하는 경우에 대한 처리를 깔끔하게 해줄 수 있었다.

사용자가 회원 탈퇴를 하는 경우에도 이 메소드가 호출되어 연결 끊기가 자동으로 이뤄지도록 하였다.

 

3. 재인증 여부 판단 로직

이 역시 pre_social_login() 메소드에서 구현한 내용으로, (뒤에서 설명할) 회원 정보 수정/삭제 페이지에 들어가기 위한 재인증 페이지에서 소셜 계정으로 재인증을 진행하였을 때를 인식하기 위한 로직이다. 이를 인식할 수 있어야 하는 이유는, 재인증이 완료된 후 적절한(일반적인 소셜 회원가입/로그인 때와는 다른) 페이지로 리다이렉트를 시키도록 하기 위함이다. 기본적인 재인증 여부 판단 로직은 다음과 같다.

 

재인증 과정에 해당하려면 먼저 해당 소셜 계정이 가입이 되어 있어야 한다. 아직 가입되지 않은 계정으로 재인증을 한다는 건 말도 안 되기 때문이다. 또한, 현재 이미 해당 소셜 계정으로 로그인이 되어 있는 상태여야 한다. 재인증 과정을 판별하는 핵심적인 두 가지 기준은 이처럼 두 가지였다(구현 상세 생략). 참고로, pre_social_login() 함수에서는 전달받는 인자 값(정확히는 SocialLogin 인스턴스)으로부터 이와 같은 정보들을 조회하는 것이 가능하였다.

 


회원 정보 관련 각종 페이지 정리 및 개선

로그인/회원가입 관련 코드를 정리한 이후, 회원 정보와 관련하여 존재하는 여러 종류의 페이지들도 정리하기 시작하였다. 기본적으로 원래도 존재하기는 했던 페이지들이기 때문에 재개발이라고 보기는 어렵고, 전반적으로 코드를 깔끔하게 정리하고 개선하는 정도로 진행하였다. 여기서는 대표적인 몇 개의 페이지들만 소개한다.

 

비밀번호 찾기 페이지, 휴면 계정 복구 페이지

 

먼저, 비밀번호 찾기 페이지와 휴면 계정 복구 페이지이다. 두 페이지를 함께 묶은 것은, 둘 다 본인을 인증하기 위한 방식으로 이메일 인증 혹은 휴대폰 인증을 사용하기 때문이다. 이메일 인증을 위해 사용하는 토큰 값은 Django가 기본적으로 제공하는 Token Generator를 사용하였으며, 휴대폰 인증은 Iamport 모듈을 이용하였다.

 

다음으로, 마이 페이지와 그것의 하위에 존재하는 페이지들이다. 사실 회원 정보 자체에 대한 정보보다는 연결된 다른 객체들의 정보가 주가 되는 페이지들이기는 하지만, 인증 관련 페이지를 정리하는데 마이 페이지 쪽을 정리하지 않는 것은 약간 불편해서 함께 정리하였다. 반응형을 고려하여 HTML, CSS 코드를 정제하는 데 집중했던 페이지들이다.

 

마지막으로, 회원 정보 수정/삭제 페이지이다. 이는 곧 재인증 페이지와도 직결됐는데, 처음으로 회원 정보 수정/삭제 페이지에 접속하려 하면 재인증 페이지로 리다이렉트 되기 때문이었다. 재인증은 (비밀번호가 설정되어 있는 계정이라면) 비밀번호로 할 수도 있고, (소셜 계정이라면) 소셜 계정으로 할 수도 있다. 그리고 한 번 재인증이 되면, 일정 시간 동안 재인증을 다시 요구하지 않도록 재인증 시각을 세션에 저장하도록 하였다.

참고로, 소셜 계정으로 재인증하는 것은 해당 소셜 계정 로그인 페이지로의 링크에 적절한 쿼리 스트링을 넣으면 이미 로그인이 된 상태여도 재로그인을 요구하도록 하는 것이 있어서 이를 활용하였다.

 


불필요한 정적(Static) 파일 제거

소셜 로그인/회원가입을 위해 사용했던 SDK 코드들을 전부 삭제하면서 혹시 현재 사용되지 않는 정적 파일들이 얼마나 더 있나 하는 호기심에 조금 찾아보았는데, 생각보다 너무 많아서 결국 참지 못하고 정리 작업에 들어갔다. 기본적인 원칙은 다음과 같았다. 정적 파일들을 하나씩 일일이 보면서, 어느 곳에서도 참조되지 않으면 삭제하는 것이다.

 

원래 특정 기능을 삭제하려면 그 기능만이 사용하던 다른 기능도 삭제해주는 것이 가장 옳은 방법이다. 예를 들어, 특정 HTML 파일을 삭제하려면 그 HTML 파일만이 사용하던 JavaScript 파일이나 CSS 파일도 함께 삭제해줘야 한다. 그런데 현실적으로 개발자분들이 항상 그 원칙을 지켜주시기란 쉽지 않다. 그렇게 쌓인 정적 파일들이 많았던 것이고, 삭제한 것들의 개수를 세어 보니 무려 800개 정도 되었다.

800개나 되는 정적 파일들을 삭제하고 나니, 실서버 배포 시에도 collectstatic 명령어가 더욱 빠르게 실행되었다. 수집하는 정적 파일들의 개수가 확 줄었기 때문에 어쩌면 당연한 결과였다.

 


▎관리자 정보 조회 및 수정 기능 개선

1. 배경

관리자 권한이란 오픈갤러리 관리자 사이트에 접속할 수 있는 권한을 말한다. 기본적으로 현재 재직 중인 직원분들은 전부 관리자 권한을 갖고 있다. 그리고 그 권한이라는 것은 기본적으로 User 모델의 boolean 필드 값에 의해 제어되는 방식이었다. 그런데 '일부' 직원분들의 경우에는 어떠한 별도의 모델을 통해 소속 팀, 직급, 역할 등의 정보가 저장되기도 하였다. 물론 그러한 모델 객체가 없는 직원분들의 경우에는 그러한 정보가 저장되고 있지 않았다. 그래도 되었던 이유는, 그러한 분들의 경우에는 관리자 사이트에 접속할 수 있는 권한만 있으면 되고 관리자 사이트에서의 업무와는 직접적인 연관이 없었기 때문이다.

관리자 사이트에서의 업무와 직접적인 연관이 있는 직원분들의 경우, 업무마다 그 업무를 수행할 수 있는 권한을 구분하기 위해 소속 팀, 역할, 직급 등의 정보가 데이터베이스에 저장되어 있어야 했다. 그렇지 않으면 모든 직원분들이 모든 업무에 손을 댈 수 있기 때문이다.

 

2. 문제점

이러한 맥락에서, 필자는 다음과 같은 것들을 문제 상황으로 인식하였다.

 

먼저, 꼭 관리자 사이트에서의 업무와 직접적인 연관이 있는 직원분들만 별도의 모델로 데이터베이스에 저장해야 하는가에 대한 의문이 있었다. 기본적으로 오픈갤러리에 재직 중인 직원분들이라면 모든 분들이 데이터베이스에 일관된 포맷으로 저장되어 있는 것이 관리 상 깔끔하다고 생각했다. 간단히 말해서, 현재 재직 중인 모든 직원분들 각각의 정보(소속 팀, 직급, 역할 등)를 조회하고 수정할 수 있는 페이지가 있는 게 좋다고 생각했는데, 누군가의 정보는 데이터베이스에 있고 누군가의 정보는 데이터베이스에 없으면 그러한 페이지를 개발하는 것조차 불가능하다는 것이다.

 

다음으로, 각 직원의 정보(소속 팀, 직급, 역할 등)를 다루는 UX가 굉장히 불편하였다. 기본적으로 관리자 사이트에서 업무를 보시는 직원분들은 소속 팀과 역할의 개념조차 헷갈려하시는 경우가 많았고, 소속 팀은 대충 알겠는데 역할이라는 것이 정확히 무엇을 위해 존재하는 기능인지 잘 모르시는 경우도 많았다. 또한, 앞서 말했듯 누군가는 그러한 정보가 별도의 모델로 표현되지 않기 때문에 소속 팀, 직급, 역할 등의 정보를 설정해주는 것조차 불가능해서 그러한 정보를 설정해줄 수 있는 사람의 기준에 대해서도 혼란이 잦았다. 그렇다 보니 신규 입사자 처리나 퇴직자의 처리 등이 언제나 깔끔하지는 못했다. 누군가는 이미 퇴사했는데도 재직 중인 것처럼 남아있던가 하는 식이었다.

사용자(직원)분들을 탓할 것이 전혀 아니다. 사용자 분들이 이 정도로 헷갈려한다면, 애초에 UX에 대한 고려가 제대로 되지 않은 채로 개발이 되었다고 보는 것이 타당하다고 생각한다. 개발자분들은 본인이 만든 기능의 편의성에 대해 사용자의 관점에서 볼 줄 알아야 하고, 어느 정도의 자기 객관화는 반드시 필요하다.

 

3. 해결

결국, 필자는 위에서 언급한 두 가지 문제 상황을 다음과 같은 방법으로 해결하였다.

 

먼저, 모든 직원분들의 정보(소속 팀, 직급, 역할 등)를 데이터베이스에 저장하는 것으로 통일했다. 관리자 사이트에서의 업무와 직접적인 연관이 있든 없든 말이다. 이 과정에서 기존에 정보가 없던 직원분들의 정보를 데이터베이스에 넣어주기 위한 마이그레이션 스크립트 파일을 작성해야만 했다.

 

다음으로, 모든 직원분들 각각의 정보(소속 팀, 직급, 역할 등)를 조회하고 수정할 수 있는 하나의 페이지를 개발하여, 관리자에 대한 설정은 이곳에서만 다룰 수 있도록 하였다(원래 존재하던 비슷한 기능의 페이지들은 전부 삭제하였다.). 언제나 그렇듯, 단순히 기능이 많다고 좋은 것은 아니다. 유사한 기능들은 하나의 원천(Single Source)에서 처리할 수 있도록 하는 것이 사용자 경험의 측면에서 훨씬 이득이다.

해당 페이지에서는 현재 재직 중인 모든 직원분들의 정보를 팀별 혹은 역할별로 나눠서 조회할 수 있도록 하였고, 필요한 경우에는 팀이나 역할 등의 정보를 편리하게 수정할 수 있도록 하였다. 또한, '현재 재직 중인 직원들'의 관리를 깔끔하게 하기 위해, 입사 처리와 퇴사 처리를 위한 별도의 UX까지 개발하였다. 단 한 번의 클릭으로 입사 처리 혹은 퇴사 처리가 되도록 함으로써 퇴사한 분이 재직 중 상태에 남아있는 등의 문제는 예방하도록 하였다. 물론, 앞서 말한 기능들 중 위험한 기능들은 어느 정도의 권한 제한을 두었다. 예를 들어, 퇴사 처리의 경우에는 특정 팀 소속의 직원분들만 진행할 수 있도록 하였다.