https://github.com/rx-make/rx-make/tree/develop
https://github.com/rx-make/starter

 

아직 General Availability 를 갖춘 상태도 아니고, 최초 세팅 시점에 치명적인 버그도 있으며,  대부분의 웹호스팅 환경에서는 사용할 수 없는 프로젝트입니다만... 이러한 시도도 있다고 알릴 겸, 여러가지 시도들이 좀 더 많이 공유되기를 원하는 마음에 한번 오픈해봅니다.

 

게시글이 꽤 장문이므로, 읽기 힘드신 분들은 슥슥 내리면서 노란색 하이라이트만 읽어도 이해할 수 있도록 작성했습니다.

 

 

 

1. Why?

 

라이믹스를 사용하며 제일 불편하다고 느낀 부분이 3가지가 있습니다.

 

1. XE에서부터 이어져오는 레거시 모듈 API들

제일, 정말 심각하게 불편하다고 느끼는 요소입니다. 그러나 현실적으로 코어단에서 해결을 바랄 수 없는 부분이기도 하지요.

게시글 작성에 사용되는 insertDocument, 댓글의 insertComment, 회원의 insertMember 등등... 오래 전부터 이어져온 메서드들 거의 대부분은 정말... 정말 사용하기 곤란합니다.
극초기 XE(1.2 즈음)부터 지금까지 오랫동안 XE/RX를 사용해왔지만, 아직도 익숙해지지 않고 (솔직히 익숙해지고 싶지도 않은) 메서드들입니다...

 

 

2. 코어와 분리하여 관리하기 힘든(귀찮은) 유저랜드 확장들

라이믹스 코어와 모듈, 애드온, 레이아웃, 스킨 등의 버전을 git을 활용해 관리하려면 꽤나 귀찮은 상황이 펼쳐집니다.

단순히 라이믹스 코어만 git으로 관리하고 서드파티들은 FTP로 관리하듯 관리하면 서드파티들은 untracked로 남겨두고, 코어만 git pull하는 것으로 끝나지만... 서드파티들도 버전 관리를 하고 싶다면, 특히 라이믹스 코어와 함께 관리하고 싶다면 이야기가 복잡해집니다.

물론 그마저도 어렵다기보단 귀찮을 뿐이지만, 굳이 신경쓰지 않아도 될 부분에 신경쓰고 있다는 생각을 떨칠수가 없더라구요.

https://github.com/rhymix/rhymix/issues/2250 와 같은 여러 논의가 있었고, 서드파티와 코어 사이의 composer 패키지 버전 충돌과 관련해서는 유의미한 발전이 있지만, 이것들이 "코어와 서드파티들을 통합한 심리스한 버전 관리"를 제공해주지는 못합니다. 애초에 코어에 바라야 하는 기능도 아니라고 봅니다.

 

 

3. 부족한 문서화, 또 그것에서 이어지는 수 많은 귀찮음

1번과도 관련되어 있는 부분입니다만, 그 중에서도 특히 문서화에 대한 부분입니다. XE의 꽤 매력적인 기능이면서도, 만악의 근원이라고 볼 수도 있는 XML들은 작성하기 너무 곤란합니다. 제대로 문서화되지 못해 매번 헤매이게 되는 info.xml, module.xml, query.xml, schema.xml 파일들은 이젠 머슬 메모리를 믿을 수 밖에 없는 상황이 되었습니다. 이건 언젠간; 이 프로젝트와는 무관하게 개인적으로 문서화하여 공유해 볼까 생각하고 있습니다.

또, 여러 (레거시) 내장 기능들이 명확한 타입 명세를 가지지 않고 있어, 이를 활용하기 위해서는 결국 해당 메서드들의 명세를 찾아가 읽어보아야 합니다.

 

 

 

2. So?

 

2번은 그렇다 쳐도 1, 3번은 아주 많은 시간과 돈과 인력을 투자해야 해결할 수 있는 문제입니다. 십수년간 쌓여온 코드베이스를 깔끔하게 정리하고, 그것들을 문서화 하는 일은 비영리 오픈소스 프로젝트에서는 사실상 불가능하다고 생각됩니다. 꽤 큰 돈을 들고 있는 워드프레스만 해도 문서화가 잘 되어 있는 듯 하지만서도 보면 볼수록 불친절하다고 느껴지는 딱히 쓸모없는 문서를 들고 있고, 애초에 PHP라는 언어 그 자체만 해도 제대로 된 문서가 아직까지도 없는 판국에, 라이믹스에 전체 문서화를 바라기는 힘들겠지요. 지금 공식 웹사이트에서 제공해주는 새로 추가된 기능들의 문서만으로도 정말 감사하게 생각하고 있습니다.

 

그럼에도 불편한 건 불편한거죠. 이 모든걸 해결할 수 없으면 다른 방법을 모색해 보아야 하는 것 아니겠습니까.

 

 

3. How?

 

먼저 현실적으로 해결하기 판단한 1, 3번은 뒷전으로 하고 2번부터 해결하기로 했습니다.

 

1. 서브모듈을 활용한 코어 관리

지금껏 코어를 메인으로 두고, 각 서드파티 자료들을 git submodule 로 등록해 관리하는 케이스는 자주 보였고, 그나마 유의미한 대안으로 논의되고 있었습니다. 그러나 이러한 방법은 자연스러운 흐름이 아니라고 생각했습니다. 특히 프로젝트당 약 3~40개의 모듈을 새로 제작해 사용하는 제 입장에서는 한 개 프로젝트에 레포를 40개를 만들어야 한다는 문제도 있고, 무엇보다 A 모듈이 B 모듈을 의존하는데, B 모듈에 BC가 생기면 정말 곤란해집니다. 각 레포의 히스토리만으로는 서로의 버전이 어떠한 영향을 끼치는지 알 수 없기 때문에, 관련하여 쉽게 추적할 수 있도록 아주 상세한 기록을 남겨야합니다.

 

그러나 반대로 코어를 서브모듈로 등록하고, 모든 서드파티 자료들을 메인 레포에 모아두면 이 모든 문제가 해결됩니다.

코어를 public/ 경로에 서브모듈로 등록하고, 모든 서드파티 자료들을 public/modules/* 에 심볼릭 링크로 등록할 수 있는 스크립트를 작성했습니다.

 

스크린샷 2024-12-11 오전 11.19.37.png  스크린샷 2024-12-11 오전 11.20.32.png

 

`./rxmake link` 명령어를 실행하면 위 이미지와 같이 src/Modules/ 디렉터리 내 모든 모듈들을 public/modules/app_modules_* 로 심볼릭 링크를 걸어줍니다.

이제 라이믹스 코어는 서브모듈을 통해 버전 관리되고, 전체 프로젝트는 코어와 서드파티 모두의 BC에서 안전하게 관리될 수 있으며, 또 딱 1번의 커맨드(`git pull && ./rxmake link`) 만으로 깔끔하게 업데이트 할 수 있게 되었습니다.

 

명령어도 아주 간단해졌겠다, 이 이점을 살릴 수 있는 작은 기능을 하나 더 추가했습니다. 깃허브 웹훅 이벤트를 듣고, 자동으로 해당 명령어를 실행해주는 기능입니다. 알파/베타 스테이징 시점에서는 아주 잦은 주기로 업데이트를 배포하게 되는데, 배포시마다 SSH에 접속하여 해당 명령어를 실행하는건 너무 귀찮죠. 또 그렇다고 거창한 CI/CD를 세팅하기엔 소잡는 칼로 닭잡는 격이기에... 간단하게 웹훅을 추가해 사용하고 있습니다.

 

위 방식들도 단점이 없는건 아닙니다. 생각보다 아주 큰 단점이 몇가지 있습니다.

먼저 모듈이 심볼릭 링크로 등록되기 때문에, 꽤 많은 구간에서 `realpath()` 를 호출하는 XE/RX가 제대로 된 경로를 확인하지 못하는 문제가 있습니다. 특히 템플릿 관련된 영역에서 큰 문제가 있었습니다.

https://github.com/rhymix/rhymix/issues/2310 이슈가 accept 되면서 덕분에 그나마 쉽게 해결할 수 있었습니다. 억지에 가까운 방법일지도 모르겠지만, https://github.com/rx-make/rx-make/blob/49819d7f6f5c41e63760f96424d6b1d43f8cad5d/src/Module/BaseModule.php#L62-L66 에서 확인해 볼 수 있어요.

 

<!> 2310 이슈에서 기진곰님이 언급하신 것 처럼 심볼릭링크는 FTP에서 심각한 문제를 일으키기도 하고, 일반적인 서드파티 자료들이 심볼릭 링크를 마음껏 이용한다는 시그널을 줘서는 안된다는 점에 저도 동의하기 때문에... 이러한 구성이 어떠한 문제를 일으킬 수 있는지 개발자가 충분히 이해하고 있어야 합니다.

 

 

2번 버전 관리는 어떻게 해결했네요. 이제 1, 3번 문제를 해결할 차례인데요.

 

 

2. 레거시 API들을 해결해 줄 Facade 작성

사실 이 접근법은 근본적인 문제 해결이 아닙니다. 결국 이미 작성된 코드를 이쁜척 하도록 시키는 것일 뿐더러,

코어에서 BC가 발생하면 이것들을 모두 따라가야 하는 유지보수 측면의 문제가 있으며, 그렇게 BC를 따라가면서 저 또한 제대로 문서화할 자신이 없는 상태라... 

언젠간 이런 방식으로라도 해결해 봐야겠다! 생각만 해 놓고 제대로 된 코드는 작성하지 않은 상태입니다ㅎㅎㅎㅎ;;

 

 

3. ORM 작성

기존 XE/RX의 XML 쿼리는 Java의 Mybatis와 쿼리 빌더 그 사이 무언가를 가지는 기능이죠. 문제는 Mybatis라고 하기엔 적절히 타이핑된 클래스에 매핑해주지 못한다는 점이고, 그렇다고 쿼리빌더라고 하기엔 SQL로 작성하는 것보다 더 불편하다는 점이겠지요.

전자의 경우 언제 추가되었는지는 확인하지 못했습니다만, executeQuery 전후 어딘가에 클래스명을 전달해주면 stdClass 대신 해당 클래스 객체에 결과값을 넣어주는 기능이 존재하므로 지금 시점에서는 해결된 문제라고 할 수 있겠네요.

얼마 전 기진곰님과 한번 뵙고 그 때 알려주셨는데요. 그 때 제가 급한 이슈로 너무 바빠서 경황이 없었습니다. 멀리까지 찾아와 주셨는데, 식사 대접도 못하고 보내드려서 그게 아직도 너무 죄송스럽네요ㅠㅠ 다음엔 제쪽에서 찾아뵙고 좀 더 깊이있는 이야기 나눌 수 있으면 좋겠습니다!

전자의 문제가 해결되었다 하더라도, 모든 문제가 해결되는 것은 아닙니다. 특히 문서화가 되지 않은 상태에서 XML 쿼리 특유의 러닝커브까지 겹치니, 사용하기 어려운 것은 동일합니다.

 

이 때문에 ORM을 작성했습니다. PHP 시장에서 대표적인 ORM인 Doctrine 이나 Eloquent 를 고민해 보았지만, Doctrine은 라이믹스에서 사용하기엔 너무 무겁고, Eloquent 는 과한 의존성 패키지들을 가져가는 문제도 있고, magic getter, setter 등을 사용하다보니 문서화에서 자유로워지고자 하는 목적에 적합하지 않다고 판단했습니다.

 

작성한 ORM은 A. SQL로 작동하며, B. 불필요하게 무거울 수 있는 동작은 배제하고, C. 자세히 문서화 하지 않아도 쉽게 이해할 수 있어야 한다는 기준 하에 작성했습니다

 

https://github.com/rx-make/rx-make/tree/develop/src/Database 에서 확인할 수 있습니다.

 

스크린샷 2024-12-11 오후 1.15.13.png

위와 같은 형태로 모델을 선언할 수 있고,

 

스크린샷 2024-12-11 오후 1.15.56.png

위와 같은 형태로 쿼리할 수 있습니다. get, find, insert, update, delete, pagination 메서드를 지원하며, get의 경우 PK를 전달하면 SomeModel|null을, find의 경우 Filter 클로저와 다른 몇몇 옵션들을 전달하면 SomeModel[]을 반환합니다. insert, update, delete의 경우 SomeModel을 전달하면 bool 타입이 반환되구요.

 

필터(WHERE clause)를 효율적으로 설정할 방법을 매번 고민했는데, PHP에서는 타입 안전하게 작성할 방법이 거의 없더라구요. 고민고민하다 그냥 평범하게 만들었습니다.

 

ORM을 도입함으로써 얻을 수 있는 부가적인 장점이 하나 있는데, 그건 레거시 API를 ORM으로 래핑할 수 있다는 점입니다. 물론 ORM이 아닌 XML 쿼리나 Raw SQL으로도 해결할 수 있고, 지금껏 코어팀에서는 이러한 방법을 사용하는걸 권장했습니다만, ORM의 깔끔한 API를 활용해 처리할 수 있다는 점에서 조금 더 효율적이라 생각합니다.

 

아쉬운 점이라면 테이블을 생성하는 등의 기능까지 구현하기에는 제약사항이 많아, 기존의 XML 스키마를 그대로 사용하기로 했습니다. 거기다 이 프로젝트는 잘 만들어진 라이믹스 기능을 새로 작성하는게 아니니까요. 지금 시점에서는 리버스님이 만들어주신 툴의 도움을 받을 수도 있고, 추후 모델에서 XML 스키마를 추출할 수 있는 스크립트를 작성하는 방안을 생각해 보고 있습니다.

 

 

4. 새로운 라우터

라이믹스 2.0? 2.1? 부터 라우터 기능이 추가되어 보기 싫은 dispSomething_very_long_module_nameWithSomethingVeryVeryLongActNameLol 같은 문제에서 자유로워 질 수 있었습니다만, 역시 XML 이라는 제겐 너무나도 험악한 존재가 방해했습니다.

 

그래서 조금의 오버헤드를 감수하더라도, 다른 프레임워크에서 일반적으로 도입하는 것과 같은 형태의 라우터를 추가하기로 했습니다.

 

https://github.com/rx-make/rx-make/blob/develop/src/Module/BaseModuleRoutes.php

https://github.com/rx-make/rx-make/blob/develop/src/Modules/GithubPuller/Routes/GithubPullerClientRoutes.php

https://github.com/rx-make/rx-make/blob/develop/src/Modules/GithubPuller/conf/module.xml

라우팅만을 담당하는 클래스를 하나 만들어, 모든 하위 요청을 해당 클래스에서 먼저 처리하도록 했습니다.

ORM의 경우와 다르게 이미 잘 만들어져 있는 FastRoute 라이브러리를 사용해 라우팅을 처리하고, 전달받은 핸들러(일반적으로 BaseModule - [ModuleObject를 확장한 클래스] -를 확장한 클래스)를 내부적으로 실행한 뒤 결과값을 덮어씌우는 유사 프록시 형태로 구현했습니다.

 

스크린샷 2024-12-11 오후 12.34.11.png

 

덕분에 IDE 도움을 받을 수 있으며, 그럼에도 대부분의 라이믹스 구조를 이어받을 수 있는 형태의 라우터를 만들 수 있었습니다.
$this->set(...), $this->get(...) 등을 그대로 활용할 수 있고, throw new InvalidRequest() 등 에러 처리도 동일하게 할 수 있습니다.

 

 

5. 기타 등등

위에서 언급한 3개 이외에도 아쉬운 점이 없지는 않습니다.

 

먼저, 시스템 설정값을 모두 관리자 페이지에서 관리해야 한다는 점이 꽤 큰 아쉬움으로 다가왔습니다. 특히 해당 값을 관리자가 수정하지 못하게 하기 위해서는 생각보다 많은 것들을 개발해야 합니다. 저는 OS 환경변수나 .env 등으로 관리할 수 있기를 원했고, 그걸 위해 개발자들에겐 아주 악독하다 받아들여지는 코드를 몇 개 작성했습니다ㅎㅎ

 

https://github.com/rx-make/rx-make/blob/develop/src/Boot.php#L146-L170
https://github.com/rx-make/rx-make/blob/develop/src/Environment/Environment.php#L25-L62

 

.env 값을 가져오는건 잘 만들어진 dotenv 라이브러리의 힘을 빌렸고, 라이믹스의 모든 시스템 설정값은 config() 함수를 통해 가져오고 있는데, 이 값을 중간에서 변조할 필요가 있었습니다. 특히 config() 가 꽤 이른 시기부터 호출되다보니, moduleHandler.init 트리거로는 충분하지 못했고, 이 때문에 https://github.com/php-mock/php-mock 에서 아이디어를 얻어 config() 함수를 살짝 변조해 주었습니다ㅎㅎ;;;

 

또, ORM과 새로운 라우터가 추가되면서 조금 더 쉽게 REST API를 작성할 수 있게 되었고, 이걸 좀 더 효율적으로 사용하기 위해 아주 작은 모듈을 하나 만들었습니다. 어차피 모든 응답은 각 모듈에서 $this->set(...)을 사용해 처리해주므로, REST API 를 위한 mid 를 만들어주는 기능 하나만을 처리하는 더미 모듈입니다. 해당 모듈을 rest라는 mid로 생성하고, 모든 REST API 요청은 해당 mid 밑에서 처리하고 있습니다. 굳이 해당 모듈이 아니라 페이지 모듈을 사용해도 아무런 차이가 없기는 합니다.

 

이외에도 조금 더 통일된 관리자 페이지, 타입 안전한 언어 파일 관리 등을 도입하려고 했으나, 생각보다 효용이 크지 않아 포기하기로 했습니다.

 

또... 권한 관리와 같은 부분은 잘 만들면 유의미한 결과를 볼 수 있을거라 생각합니다만,

제가 당뇨 진단을 받고 몸 상태가 꽤 많이 안좋아지다보니... 기존의 형태로도 어느정도 구현할 수 있으며, 또 관련된 코어 업데이트가 존재하는 시점에서 여기까지 손대긴 쉽지 않네요ㅠ

 

 

4. Conclusion?

길게 작성했지만, 핵심은 3가지입니다.

1. 코어를 서브모듈로, 서드파티를 메인으로 하는 모노레포 관리

2. XML 쿼리 대신 ORM

3. 외부 라우터

 

GA가 갖춰지지 않은 프로젝트이지만, 내부적으로 사용하기에는 충분하다 판단되어 해당 프로젝트를 활용하여 총 다섯개의 서비스를 런칭했습니다. 성능이나 관련 이슈들은 제쳐두고, DX 측면에서 정말 만족스러웠습니다. 5개 서비스를 제작하며 겪은 DX가 기존에 비해 월등히 좋다보니 아마 한동안은 계속 사용하리라 생각합니다.

 

아마 라이믹스 코어팀에서는 이러한 형태의 사용을 권장하지 않을거고, 저 또한 이러한 사용이 일반적인 대부분의 상황에서는 적합하지 않음을 이해하고 있습니다만, 그럼에도 경험을 공유하는 이유는 최근 많이 줄어든 라이믹스 정보 공유가 다시 조금이라도 살아났으면 하는 마음이기 때문입니다.

 

어디 숨어계신 찐고수분 안계시나요~ 썰 좀 풀어주세요~ 😁

 

download.jpeg

 

  • Lv37

    이렇게 마개조해서 쓰실 수 있는 분과, FTP 접속조차 어려워하는 초보자가 공존한다는 것이 라이믹스의 매력이자 가장 큰 고민이지요. 초보자의 접근성을 떨어뜨리지 않는 범위 내에서, 마개조 수요도 최대한 반영하려고 노력하겠습니다. 예를 들어 환경변수를 통한 시스템 설정 조작(?)은 현재 구조에서도 충분히 지원할 수 있을 것 같습니다.

     

    파사드는 말씀하신 것과는 반대 방향으로(!!) 오래 전부터 점진적으로 적용해 나가고 있습니다. 예를 들어 CacheHandler의 구닥다리 API를 랩핑하기 위해 Rhymix\Framework\Cache라는 파사드를 만든 것이 아니라, Rhymix\Framework\Cache에 대한 하위호환 레이어를 제공하기 위해 CacheHandler라는 파사드를 두고 있는 것이죠. 기존의 API를 유지보수하면서 거기에 맞추어 파사드까지 관리하려면 말씀하신 것처럼 유지보수 부담이 크기 때문에, 바뀔 가능성이 낮은 기존 API를 차라리 파사드로 대체하고, 실제 기능은 모두 다른 곳에 깔끔하게 새로 구현하는 식입니다.

     

    모듈도 마찬가지로, admin 모듈처럼 커스터마이징 확률이 낮은 곳부터 먼저 대체하고 있습니다. 라이믹스 2.1에서 admin.admin.model.php에 있는 대부분의 함수들은 파사드에 불과하고, 실제 기능은 새로 구현된 클래스로 옮겨 놓았지요. 확 바꿔봤는데 의외로(?) 하위호환성 문제가 전혀 보고되지 않아서, 2.2에서는 module 모듈 등 공개된 페이지를 생성하지 않는 모듈들을 중심으로 더 본격적으로 칼을 대보려고 합니다.

     

    이렇게 대부분의 기존 API를 파사드로 대체해 놓으면, 기존 API에 대한 매뉴얼 따위는 필요하지도 않겠지요. 새 API에 대한 매뉴얼만 있으면 되니까요. 네임스페이스를 넣어서 새로 만든 클래스들은 대부분 타입 선언이 제대로 되어 있고 주석도 꼼꼼하게 작성해 놓아서, 거의 자동으로 문서 생성이 가능할 것이라고 희망회로를 돌리고 있습니다. ㅎㅎ

     

    건강 잘 지키시길 바랍니다!^^

  • Lv37 Lv7
    코어단에서 환경변수 관련 기능을 제공해주면 정말 좋겠네요! 제 마개조 프로젝트에서는 관리자 페이지의 동작을 억지로 틀어막아야 하는 점이 항상 아쉬웠거든요.
    말씀주신 파사드 형태 너무 좋습니다. 빨리 많은 부분에 반영되었으면 좋겠네요ㅎㅎ..

    기진곰님도 항상 건강하세요! 응원합니다~
  • Lv7 Lv37

    환경변수를 어떻게 넣어야 하는지에 대한 규약이 전혀 없는 상태이고, 규약을 만든다고 해도 혼란스러울 것이 분명하기에 (환경변수에 config.php와 같은 복잡한 자료구조를 담기에는 아무래도 어려움이 있죠)

    우선 Config::init() 도중이나 직후, 아무리 늦어도 Context::init() 전후에 끼어들 수 있는 방법을 제공하고, 거기서 설정을 어떻게 조작하는지는 각자 알아서 하는 것이 어떨까요? 가장 간단한 방법은 XE 시절부터 지원하던 config.user.inc.php 인클루드 시점을 Config::init() 직전이 아닌 직후로 바꾸는 것인데, 혹시 이렇게 했을 때 문제가 되는 부분이 있을지 검토해 봐야겠습니다.

     

    ORM은... 음... 공식적으로는 "순정이 아닌 또다른 언어나 문법을 배우도록 요구하는 것"이 되기 때문에 서드파티 솔루션의 레벨에 머물러야 할 것 같습니다. 개인적으로는 모델 클래스를 일일이 작성할 필요 없이, 이미 있는 XML 스키마를 기반으로 기본적인 CRUD 구조가 자동 생성된다면 무척 편리하겠다는 생각이 듭니다.

     

    어쩌면 기본적인 CRUD 정도는 코어에서도 지원할 만 하겠네요. 마치 addTable(), addIndex() 하듯이 "$table에서 PK가 $x인 레코드 뽑아와", "$table에 이 튜플을 인서트해" 하면 되니까요.

  • Lv2
    ('' ) 고수들에 대화는 언제나 심오하네요.. ㅎㄷㄷ