인디스쿨 인증센터 개발 후기

김재동 • January 31, 2019

indischool laravel xpressengine

몇 개월간의 노력 끝에 새로운 인증센터 개발이 끝났다. 개발 중간에 휴대폰 본인인증 서비스 신청을 하고 2주 정도 어쩔 수 없이 대기해야 하는 시간이 있었는데 그때 Netflix에 빠져 약간의 권태기(?)가 오기는 했지만 겨울방학이 끝나기 전에 개발을 마쳐 다행이다. 이번에 인증센터를 왜 다시 개발해야만 했는지, 그리고 어떤 것들에 중점을 두었고 새롭게 알게된 것들은 무엇인지 스스로를 위해 정리해보고자 한다.

왜 다시 개발을?

기존에 인디스쿨에서 인증을 받으려면 나이스 인사기록의 근무사항을 PDF로 제출해야했다. 하지만 이 파일은 인사기록이다보니 교사인증에 필요한 것보다 더 많은 개인정보가 담겨져 있었고 인디스쿨에서 어떠한 처리나 수집을 하지 않더라도 제출하는 사람이나 받는 사람 모두 마음이 편한 방식은 아니었다.

게다가 1인당 1계정만 가질 수 있도록 해야하는데 고유키 역할을 했던 나이스 개인번호는 엄밀히 따지면 고유키가 아니었다. 시도교류를 통해 다른 지역에 파견을 가거나 임용 전에 기간제 교사로 근무하거나 할 때는 다른 나이스 개인번호를 부여받게 된다.

인증센터는 제일 처음에는 XE의 모듈로 개발했다가 다음 버전에서 Laravel과 XE 모듈이 동시에 작동하는 방식으로 변경되었다. 그러다보니 같은 역할을 하는 코드들이 여기저기 흩어져 있었고 특히 첫 번째 버전인 XE 모듈의 코드는 보면 볼 수록 마음이 아파지는 그런 코드(스파게티)였다. 게다가 간편인증은 Laravel에서 처리했지만 서류인증은 XE의 게시판에 파일을 첨부하고 애드온을 이용해 처리하는 방식이어서 인증 처리가 한 곳에서 이루어지지 않는다는 문제도 있었다.

그래서 처리 및 수집하는 개인정보를 최소화하고 1인당 1계정 원칙을 지킬 수 있도록 본인 인증을 강화하며 인증에 관련된 코드를 한 곳으로 모으고 재사용이 용이한 코드로 변경하기 위해 인증센터를 다시 개발하게 되었다.

개발의 방향

객체지향 프로그래밍의 원칙(SOLID) 지키기

XE 모듈로 개발했던 첫 번째 버전은 클래스를 이용해 객체지향 프로그래밍의 형태만 갖추었을 뿐 사실상 인증처리 과정이 거의 하나의 메서드에서 순차적으로 이루어지는 순차적 프로그램에 가까웠다. authorize() 같은 메서드에 수십 줄의 코드가 있었다. 완전히 똑같은 코드는 아니지만 흐름이 비슷한 코드가 여기저기에 흝어져 있기도 했다.

Laravel로 개발한 두 번째 인증센터의 코드도 전보다는 많이 개선되었지만 SOLID 원칙에 위배되는 것들이 많았다. Open-Closed 원칙 같은 경우 확장은 쉬워야하고 가능한 코드의 수정이 이루어지지 않도록 되어야 하는데 많은 클래스에 변경될만한 여지가 남아 있었다.

하지만 객체지향 프로그래밍에 대한 경험이 부족해서 이 원칙을 지키는 것이 쉽지만은 않았다. 특히 간편인증을 처리하는 코드를 Controller 클래스에 담아야 할지 말아야할지 고민하며 파일을 새로 만들었다 지웠다하는 과정이 지루하게 반복됐다.

간편인증과 서류인증 둘 다 '인증'한다는 공통된 행위가 있으니 처음에는 Actor를 Authenticator라고 생각하고 Authenticator라는 추상 클래스를 만들고 공통 메서드를 담은 후 이를 상속하는 간편인증/서류인증 클래스를 만들었었다. 하지만 시간이 지날수록 생각보다 클래스가 복잡해지고 변경될 수 있는 부분들이 많이 늘어났다.

간편인증과 서류인증의 공통 메서드를 뽑아 내려고 하다보니 오히려 코드가 더 복잡해지는 것 같았다. 왜냐하면 간편인증은 급여명세서를 첨부하자마자 파일을 분석하고 인증이나 예외처리를 하지만 서류인증은 먼저 신청서와 파일을 제출하고 후에 관리자가 승인을 하거나 반려하기 때문이다.

고민 끝에 Authenticator 클래스를 쪼개기로 했다. 처음 DB의 테이블에 인증에 대한 정보가 저장되는 부분과 서류인증에서 나중에 운영진이 인정/반려 처리하는 부분을 각각 RegistrableReviwer 클래스로 나누었더니 코드가 비교적 깔끔해지고 재사용성이 높아졌다.

나름 SOLID 원칙을 지키며 클린 코드를 작성했다고 생각하지만 곳곳에 '일단 급한 불을 끄자.'는 심정으로 땜질한 코드들이 있다. 하지만 코드를 수정하는 것에는 끝이 없을 것이다. 제대로 동작하고 이정도면 됐다 싶어서 넘어가기로 했다.

깔끔하게 작성된 코드를 보면 마치 애플 제품의 내부 기판을 보는 것 같은 느낌이 든다. 겉만 보는 일반 사용자에게는 아무것도 아닌 것이지만 그걸 만든 사람에게는 괜히 으쓱거리게 되는 뿌듯함이 있다. 그냥 자기 만족이지만.

Laravel로 모든 인증 과정 통합, XE 모듈은 최소화

간편인증과 더불어 서류인증까지 인증센터에서 처리할 수 있도록 처음부터 코드를 새로 짰다. 가장 기본적인 CRUD의 형태이지만 파일 첨부가 있어서 조금 재미있었다. 문제는 서류인증을 검토할 때 경력증명서를 이미지 파일이 아니라 PDF 파일로 올리는 경우였다. 이미지야 브라우저에서도 기본적으로 화면에 보이니까 괜찮은데 PDF 파일은 바로 볼 수 없어서 다운로드를 받거나 브라우저에서 클릭을 해서 새로운 탭으로 열어야만 했다. 조사를 해보니 PDF를 인라인 형태로 볼 수 있게 도와주는 자바스크립트 모듈이 있었다. 모바일이나 테블릿의 브라우저는 지원하지 않지만 검토는 PC에서 하면 되니까 큰 문제는 아니었다.

XE에 있던 인증 모듈은 사실 삭제해도 무방했지만 XE를 쓰는 이상 한 가지 걸리는 점이 있었다. 바로 캐싱인데 XE에서는 회원 그룹에 관한 정보도 캐싱을 한다. 그런데 인증센터에서 아무리 회원 그룹 정보를 DB에서 변경해도 캐시가 갱신되지 않으면 그룹이 변경되지 않은 것처럼 나온다. 그래서 로그아웃을 한 번 했다가 다시 로그인 하거나 관리자가 직접 캐시를 재생성해야 한다. 이걸 Laravel에서 구현해보려고 XE의 캐시 부분 코드를 살펴보았는데 cache key에 현재 사용중인 XE의 버전 문자열이 사용된다는 것을 알게 되었다.

/**
 * Get cache name by key
 *
 * @param string $key The key that will be associated with the item.
 * @return string Returns cache name
 */
function getCacheKey($key)
{
    $key = str_replace('/', ':', $key);
    return __XE_VERSION__ . ':' . $key;
}

/**
    * Put data into cache
    *
    * @param string $key Cache key
    * @param mixed $obj   Value of a variable to store. $value supports all data types except resources, such as file handlers.
    * @param int $valid_time Time for the variable to live in the cache in seconds.
    * After the value specified in ttl has passed the stored variable will be deleted from the cache.
    * If no ttl is supplied, use the default valid time.
    * @return bool|void Returns true on success or false on failure. If use CacheFile, returns void.
    */
function put($key, $obj, $valid_time = 0)
{
    if(!$this->handler && !$key)
    {
        return false;
    }
    $key = $this->getCacheKey($key);
    return $this->handler->put($key, $obj, $valid_time);
}

그러니 Laravel에서 매번 XE의 버전을 알아내기도 그렇고 해서 XE 모듈에서는 API를 통해 멤버 캐시를 삭제하는 역할을 하도록 하였다.

휴대폰 본인인증

휴대폰 본인인증이 없었더라면 아마 이번 프로젝트는 시작도 하지 않았을 것이다. 휴대폰 본인인증을 받으면 특정 개인임을 나타내는 고유한 문자값을 돌려준다. unique_key라고 하는데 어떤 사이트에서 받든 동일한 unique_key와 인증받는 사이트마다 고유한 unique_in_site라는 고유키가 있다. 휴대폰 본인인증과 관련해서 몇 가지 궁금한 것이 있었는데

결론은 휴대폰이 여러 개이어도, 통신사를 바꿔도 고유값에는 변화가 없다는 것이다. 즉 휴대폰 본인인증 결과로 돌려받는 값은 주민등록번호를 일정한 공식으로 해싱한 결과값과 같다고 생각해도 된다.

휴대폰 본인인증 서비스는 아임포트를 통해 간편하게 연동할 수 있었다.

Testing

기존 인증센터도 phpspec을 통해 테스트를 하고 있었다. 다만 간편인증에서 사용되는 PDF parser 클래스에 대한 Unit 테스트만 있었기 때문에 이번에는 PHPUnit을 이용해 Feature 테스트까지 작성을 했다. 인증센터에서 일어나는 100%를 커버하지는 않지만 핵심적인 것들은 테스트에 모두 포함되었기 때문에 리팩토링에 큰 도움이 되었다. 물론 클래스 자체를 바꾸거나 매서드가 변경되었을 때는 테스트도 바꾸어야 했기 때문에 손이 아예 안 가는 것은 아니었지만 테스트 덕분에 리팩토링에 드는 시간을 많이 줄일 수 있었다.

Laravel의 다양한 기능 활용

이번에는 Laravel에서 제공하는 다양한 기능들도 사용해 보았다. 대표적인 것이 인증 처리 과정에서 Event를 발생시키고 Listener를 통해 이메일을 발송하는 것이다. 서류 신청이 들어왔을 때 Slack으로 알리기 위해 Notification도 사용했다. 그리고 이메일과 Slack으로 보내는 작업은 Queue를 사용했다.

Production 환경에서 Queue를 사용할 때는 오류가 발생했다. 서류인증을 처리한 사용자의 이름을 Slack으로 보내는 부분이 있는데 거기서 오류가 발생했다. 오류 내용으로 검색을 해봐도 해답을 찾기가 쉽지 않았는데 나중에 알고보니 Queue로 넘겨진 이후에는 세션에 저장되는 로그인 정보를 알 수 없었기 때문이었다. 예전에 iOS 프로그래밍을 잠깐 했을 때 UI 업데이트를 메인 스레드에서 하지 않아 오류를 겪었던 것이 문득 떠올랐다.

인증 만료일이 얼마 남지 않았음을 알리는 리마인더 이메일 발송과 인증 만료 후 상태 변경을 위해 Schedule 기능도 사용해 보았다. 이런 저런 기능들이 기본적으로 포함되어 있다보니 Laravel이 참 편리한 프레임워크라는 생각이 들었다.

Vue

처음에는 SPA(Single Page Application)로 만들어 볼까 싶었지만 인증센터로 굳이 그렇게까지 할 필요가 없었기 때문에 자바스크립트가 쓰이는 부분에 jQuery 대신 사용하였다. 폼 버튼에 주로 컴포넌트 형태로 사용하였는데 다음 프로젝트가 무엇이 될지는 모르겠지만 언젠가 SPA에 적합한 프로젝트가 나타난다면 Vue를 적극적으로 사용해보고 싶다.

다음 프로젝트

시간을 쪼개어 개발을 하다보니 별 것 아닌 것에도 스트레스를 꽤 받았다. 특히 나는 성미가 급해서 한 번 시작한 일은 빨리 끝을 보고 싶어하기 때문에 계속 집중할 수 있는 환경이 필요한데 지금 나의 상황은 그렇지 못하기 때문이다. 그래서 당분간은 소소하게 인증센터 유지보수를 하며 책도 읽고 좀 쉬고 싶다(감옥에 가야 다 읽을 수 있을 것만 같았던 토지를 요즘 다시 읽고 있다). 책을 읽고 글을 쓰는 것이 지루해질 때 쯤이면 다시 다른 프로젝트를 시작할지도 모르겠는데 그건 아주 큰 건이 될 것 같다.