What is scalability for a mobile developer
Copied from my Facebook post.
Iโve had thoughts about what scalability means to a mobile developer for a long time. I started my career at Line because I wanted to experience dealing with thousands of global users. But actually, the traffic of thousands of people was something that a server developer had to deal with. I was a little disappointed.
A server developerโs skill is measured by how much traffic one has handled, but for a mobile developer, I felt like there was not much difference in skills or code in whether one develops an app used by 100 people or 1 million people. So I wondered what it means to create a scalable code or system in a mobile app.
A developerโs skill is concentrated in the section where bottlenecks occur. Because users get crowded in one place at the same time, the server gets loaded, and the skill that can solve this load is valuable. But an app does not get loaded because there are a lot of users. This is because the app gets delivered to the userโs doorstep, not the users getting crowded in an app.๐
Then where is the bottleneck that needs to be handled by mobile developers? Ironically, I thought the bottlenecks in a mobile app occur not because of the users, but the developers. I thought a point where a lot of developers push their code into an app that gets deployed as a single program could be seen as a bottleneck.๐
Just as the serverโs structure and codes should be different when there are 1,000 users and millions of users, an appโs development environment and code structure should be completely different when there are only 3 developers and 100 developers. If not, the consequences of the bottleneck pass on to the developer and the users as well. The first app startup time increases and the app size keeps increasing. Not only that, more crashes happen, decreasing user satisfaction, or you may have to go through hotfixes the whole time due to the side effects that keep showing up. Also, build time increases, decreasing the developerโs productivity and quality of life due to stress. On top of that, when 100 people are pushing on the master branch, even QA, dealing with bugs, and deploying apps arenโt simple tasks. This is because someone is pushing in codes with developed features while someoneโs cherry-picking while fixing bugs. It doesnโt have to go up to 100 people. Even if there are more than just 10 developers, problems like these start to show up.
So I think I recently found a clue to my curiosity. I thought what scalability to a mobile developer means is that as a company grows and the mobile team grows, itโs keeping both the user experience and the developer experience to not deteriorate, and keep deploying the app quickly with confidence.
Tags: scalability, mobile dev
uber/RIBs ์ ๋ ํ ์คํธ ์ง๊ธฐ
RIBs๋ ์ฐ๋ฒ์์ ๊ฐ๋ฐํ๊ณ ์คํ์์ค๋ก ๊ณต๊ฐํ ๋ชจ๋ฐ์ผ ์ํคํ ์ฒ ํ๋ ์์ํฌ๋ค. RIBs ํ๋ ์์ํฌ๋ ์ฑ์ ๋ณต์กํ ์ํ ๊ด๋ฆฌ์ ๋น์ฆ๋์ค ๋ก์ง์ RIBs ๋ฉ์ด๋ฆฌ(์ดํ Riblet)๋ก ๋ถ๋ฆฌํ ๋ค ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ์ฐ๊ฒฐ์ํจ๋ค. ํ๋์ Riblet ๋จ์๋ฅผ ๊ตฌ์ฑํ๋ ๊ฐ์ฒด์ ๊ฐ๊ฐ์ ์ญํ ์ ์๋์ ๊ฐ๋ค.
RIBs์ ๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ
์ ๋ํ์ฒ๋ผ ๊ฐ RIBs ๊ฐ์ฒด๋ ์ญํ ์ด ๋๋ ทํ๊ฒ ๋๋ ์ ธ ์๋ค(Single Responsibility
). ๊ทธ๋ฆฌ๊ณ ํ์ดํ๋ก ํ์๋ผ ์๋ ๊ฐ ๊ฐ์ฒด์ input์ output์ ํ๋กํ ์ฝ๋ก ์ถ์ํ ๋ผ์์ด์(Dependency Inversion
) ๋ฐ๋ก ๋ผ์ด๋ธ ํ mocking ๊ธฐ๋ฒ์ ํตํด ๋
๋ฆฝ์ ์ผ๋ก ํ
์คํธํ๊ธฐ ์ข๋ค. ๋ํ ๋ถ๋ชจ์ ์์ Riblet์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ก ๋ถ๋ฆฌ(decoupling) ๋ผ์์ด์ ๋ถ๋ชจ Riblet์ ์ฝ๋ ์์ ์ ์ต์ํํ๋ฉด์๋ ๋ณต์กํ ์์ Riblet์ ์๋ก ๋ง๋ค๊ฑฐ๋ ์์ ํ ์ ์๋ค(Open-Closed Principle
). ๋ ๋ฌ ์ ๋ ํ๋ก์ ํธ๋ฅผ ํด๋ณธ ๋ฐ ๋๋์ ์ RIBs ์ํคํ
์ฒ๋ฅผ ์ฐ๋ฉด SOLID์์ ๋น์ค์ด ํฌ๊ณ ๊พธ์คํ ์งํค๊ธฐ ์ด๋ ค์ด SRP, OCP, DIP ์ธ ๊ฐ์ง ์์น์ ๋ฐ๊ฐ์ ์ ์ผ๋ก ์งํค๊ฒ ๋๋ค. ๋ฌผ๋ก ์ด๊ธฐ๊ธฐ๋ ์ฝ๋ค. ์ด๋ค ์ํคํ
์ฒ๋ฅผ ์ฐ๋ ๊ฐ๋ฐ์๊ฐ ์ฝ๋๋ฅผ ์ง๊ธฐ ๋๋ฆ์ด๊ณ ๋จ์ง ์ํคํ
์ฒ๋ฅผ ๋์
ํ๋ ๊ฒ๋ง์ผ๋ก ๋ด ์ฝ๋๊ฐ ์ข์์ง์ง ์๋๋ค.
๋ฌด์์ ํ ์คํธ ํด์ผํ ๊น
RIBs ์ํคํ ์ฒ๋ฅผ ๊ตฌ์ฑํ๋ ๊ฐ์ฒด๋ค์ ๋จ์ ํ ์คํธํ๊ธฐ ์ ๋ง ์ฉ์ดํ๋ค. ์ญํ ์ด ๋ช ํํ๊ณ ์๊ฒ ๋๋์ด์๊ณ ๋ถ๋ชจ์ ์์ Riblet์ด ์ฝํ๊ฒ ์ปคํ๋ง๋ ๋งํผ, ๋์ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋ฌ์ฑํ ์ ์๋ค. Riblet๋น ์ต์ 4+ ๊ฐ์ ํ ์คํธ ํด๋์ค๊ฐ ํ์ํ๋ฐ Xcode์ ์ฝ๋์์ฑ ํ ํ๋ฆฟ์ ์ฐ๋ฉด ๋งค๋ฒ ๋ง๋ค๊ธฐ ๋ฒ๊ฑฐ๋ก์ด ์ ๋ ํ ์คํธ ํด๋์ค์ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๊น์ง ์๋์ผ๋ก ์์ฑํด์ค๋ค. ๊ทธ๋ฐ๋ฐ ์ฒ์ ํ๋ก์ ํธ๋ฅผ ํ ๋๋ ์ด ๋ง์ ํ ์คํธ ํด๋์ค์ ๋ญ ์ฑ์ ๋ฃ์ด์ผํ๋์ง ๋ชฐ๋ผ ๋ง๋งํ๋ค. ํ์ง๋ง ์ฝ๋ ๋ฆฌ๋ทฐ๋ ๋ฐ๊ณ ๊ธฐ์กด ์ฝ๋๋ ์ดํด๋ณด๋ฉด์ ๊ท์น์ ๋ฐ๊ฒฌํ๊ณ ์ด๋ฅผ ํตํด ๊ฐ ํด๋์ค์ ์ญํ ๊ณผ ๋ชฉ์ ์ ๋ฐ๋ผ ์ด๋ค ๋ฐฉ์์ผ๋ก ํ ์คํธ ํด์ผํ๋์ง ํฐ๋ํ๋ค.
Router Test: ์์ Riblet ๋ผ์ฐํ
Router์ ํ
์คํธ๋ ๋น์ฆ๋์ค ๋ก์ง์ ๋ง์ถฐ ์์ Riblet์ ๋๋ค(attachChild
) ๋ถ์๋ค(detachChild
) ํ๋ ๋์์ ๊ฒ์ฌํด์ผ ํ๋ค. Router ๊ฐ์ฒด ์ค๋ช
์ ๋ณด๋ฉด โ๋ผ์ฐํฐ๊ฐ ์์ ๋ผ์ฐํฐ๋ฅผ ๋ง๋ค๋๋ ๊ผญ helper builder๋ฅผ ์จ์ผ ํ๋ค.โ(๋งํฌ)๋ ์ฃผ์์ด ์๋ค. ์์ Riblet์ ์์ฑํ ๋ xxBuilder ํด๋์ค๋ฅผ ์ง์ ์ฐ์ง๋ง๊ณ xxBuildable๋ก ์ถ์ํํ์ฌ ์ฃผ์
๋ฐ์์ผ ํ๋ค๋ ๋ง์ด๋ค. ์ด๋ ๊ฒ ํด์ผ ํ
์คํธ ํ๊ฒฝ์์ xxBuildableMock์ ์ฃผ์
ํด์ ๋ผ์ฐํฐ๊ฐ ์์ Riblet์ ์ ๋๋ก ์์ฑํ๋์ง ๊ฒ์ฌํ ์ ์๋ค. Router๋ interactor์ ์์ฒญ์ ๋ฐ๋ผ ๋ผ์ฐํ
์ ๋์ ํด์ฃผ๋ ์ญํ ์ด๊ธฐ ๋๋ฌธ์ ์ด๊ฒ ์ธ์ ๋ค๋ฅธ ๋ก์ง์ ์๋๊ฒ ๋ฐ๋์งํ๋ค.
Interactor Test: ๊ฐ์ข ๋น์ฆ๋์ค ๋ก์ง
Interactor๋ ์ฑ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๋นํ๋ ๋ถ๋ถ์ด๋ผ ๋ค๋ฅธ ํด๋์ค๋ณด๋ค ๋ณต์กํ๊ณ ๊ฐ๋ฐ์์ ์์ ๋๊ฐ ๋๋ค. ๊ทธ๋์ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ฒ๋ผ ์ ํํํ๊ธฐ๊ฐ ์ฝ์ง ์์ง๋ง ์์ฃผ ์ฌ์ฉ๋๋ ๋ช ๊ฐ์ง๊ฐ ์๋ค.
-
Interactor๋ ์์ interactor์ ์ ๋ณด๋ฅผ ์ ๋ฌํ๊ธฐ ์ํด ๋ฆฌ์กํฐ๋ธ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ์ฌ์ฉํ๋ค(์ฐธ๊ณ : ๊ณต์ ๋ฌธ์). ๋ฆฌ์กํฐ๋ธ ์คํธ๋ฆผ์ ํ์ฉํ๋ฉด ๋ถ๋ชจ์ ์์ interactor๊ฐ ์ง์ ์ ์ธ ์์กด ๊ด๊ณ(direct coupling)๋ฅผ ๋งบ์ง ์์ ์ ์๋ค. ์คํธ๋ฆผ์ ์ด๋ป๊ฒ ๊ตฌํํ๋ ๊ฐ๋ฐ์์ ์์ ์ง๋ง ๋ณดํต์ RxSwift๋ฅผ ์ฐ๋ฉด ์ข๋ค. RIBs ์ํคํ ์ฒ๋ Rx๋ฅผ ์ฐ๊ณ ์์ด์ ์ด๊ฑธ ์ฐ๋ฉด ๊ตณ์ด ๋ฆฌ์กํฐ๋ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ๋ฌ๊ฐ ์ถ๊ฐํ ํ์๊ฐ ์๋ค. ํ ์คํธ ํ๊ฒฝ์์๋ ์คํธ๋ฆผ์ mockingํด์ interactor๊ฐ ์ ๋๋ก ๊ฐ์ ๋ด๋ณด๋ด๋์ง ํ์ธํ ์ ์๋ค. ๋ฐ๋๋ก ๋ถ๋ชจ interactorํํ ์ ์ฃผ์ ๋ฐ์ ์คํธ๋ฆผ์ subscribeํด์ ์์ ์ ์ฒ๋ฆฌํด์ผํ๋ ๊ฒฝ์ฐ๋ผ๋ฉด ๋ง์ฐฌ๊ฐ์ง๋ก mock ์คํธ๋ฆผ์ ์ฃผ์ ํ ๋ค ํ ์คํธ ์ผ์ด์ค์์ ๊ฐ์ ๋ฐ๊ฟ๊ฐ๋ฉด์ ๋ฐ์ดํฐ์ ๋ง๊ฒ ์ ์ฒ๋ฆฌํ๊ณ ์๋์ง ๊ฒ์ฌํ๋ค.
-
Interactor๋ view๋ก๋ถํฐ ์ฌ์ฉ์ ์ ๋ ฅ์ ์ ๋ฌ๋ฐ๋๋ค. ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ฐ๋ผ ์ ์ ํ ์์ ์ด ์คํ๋๋์ง ํ์ธํ๊ธฐ ์ํด์ interactor์ view listener ๊ด๋ จ ๋ฉ์๋๋ฅผ ํ ์คํธ ์ผ์ด์ค์์ ์ง์ ํธ์ถํด์ค ๋ค ํ์ํ ์์ ์ด ์คํ๋๋์ง mocking๋ ์์กด์ฑ ๊ฐ์ฒด๋ฅผ ํ์ธํ๋ค.
-
Interactor๋ router์ presenter(ํน์ view)๋ฅผ ์์ฃผ ํธ์ถํ๋ค. ๋ทฐ๋ฅผ ๋๋ค ๋ถ์๋ค ํ๊ธฐ๋ ํ๊ณ UI๋ฅผ ์ ๋ฐ์ดํธํ๊ธฐ๋ ํ๋ค. ๊ทธ๋์ router์ presenter ๋ฉ์๋๊ฐ ์๋์ ๋ง๊ฒ ๋ถ๋ฆฌ๋์ง ํ์ธํ๋ค. ์ด ๊ฒฝ์ฐ์๋ ๋จ์ํ ๋ถ๋ ธ๋์ง ์๋ถ๋ ธ๋์ง ํ์ธํ๋ ๊ฒ๋ณด๋ค๋ ๋ถ๋ฆฐ ํ์๋ฅผ ์ ํํ ๊ฒ์ฌํ๋๊ฒ ์ข๋ค. UI ์ ๋ฐ์ดํธ๋ฅผ ๋ถํ์ํ๊ฒ ์ฌ๋ฌ๋ฒ ํ๋์ง ํ์ธํ ์๋ ์๊ณ ์ค๋ณต์ผ๋ก ๋ทฐ ๋ผ์ฐํ ์ ํ์ง ์๋์ง๋ ํ์ธํ๋ค. (์ฐธ๊ณ : ๊ณต์ ํํ ๋ฆฌ์ผ Mock ๊ฐ์ฒด)
Builder Test: Concrete ํด๋์ค ์์ฑ๊ณผ ์ฃผ์
Builder๋ RIBs ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ์ฐธ์กฐ์ ์ ์ฐ๊ฒฐํด์ฃผ๊ณ , ์์กด์ฑ ์ฃผ์
์ ํ์ํ concrete ํด๋์ค๋ฅผ ์์ฑํ๋ ์ญํ ์ด๋ค. ๋ฐ๋ผ์ is
๋ as?
๋ฅผ ์จ์ ์ฌ๋ฐ๋ฅธ ํด๋์ค ํ์
์ด ์์ฑ๋๋์ง ํ์ธํด์ฃผ๋ ๋ฐฉ์์ผ๋ก ๋์์ ๊ฒ์ฌํ๋ค.
Presenter Test: ๋ทฐ ๋ชจ๋ธ ์์ฑ ๋ก์ง
๋ฐ์ดํฐ ๋ชจ๋ธ์ด ๋ทฐ ๋ชจ๋ธ๋ก ์ ๋ณํ๋๋์ง ํ์ธํ๋ค. ๋ฐ์ดํฐ ๋ชจ๋ธ์ UIKit ํด๋์ค๋ฅผ ์ฐ์ง ์๋๋ก ๋ง๋ค๊ณ , ๋ทฐ๋ฅผ ๊ตฌ์ฑํ ๋ ํ์ํ UIKit, Core Graphics ํ์ ๋ฑ์ ๋ทฐ ๋ชจ๋ธ์์ ๊ฐ์ง๊ณ ์๋๊ฒ ์ข๋ค. ์๋ฅผ ๋ค์ด ๋ฐ์ดํฐ ๋ชจ๋ธ์ hex ๋ฌธ์์ด๋ก ์์ ๊ฐ์ ๊ฐ์ง๊ณ ์๊ณ presenter์์ ์ด๋ฅผ UIColor๋ก ๋ณํ์ ํ๋ค. ๋๋ ๋ฐ์ดํฐ ๋ชจ๋ธ์ด Date ํ์ ์ผ๋ก ๋ ์ง๋ฅผ ๊ฐ์ง๊ณ ์๊ณ presenter์์ DateFormatter๋ฅผ ํตํด ๋ฌธ์์ด๋ก ๋ณํํด์ ๋ทฐ ๋ชจ๋ธ์ ์ ์ฅํ๋ค. ์ด๊ฐ์ ๋ณํ ๋ก์ง์ ๊ฒ์ฌํด์ผ ํ๋ค.
View Test: ๋ทฐ์ ๋ ๋๋ง
View๋ ์ฝ๋๋ง์ผ๋ก๋ ์ ์๋ฏธํ ํ ์คํธ๋ฅผ ํ๊ธฐ ์ด๋ ต๋ค. ์ค๋ ์ท ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐ๋ฉด ๋ทฐ๋ฅผ ๋ ๋๋งํด์ ์ด๋ฏธ์ง ํ์ผ๋ก ์ ์ฅํด๋๊ณ , ํ ์คํธ๋ฅผ ๋๋ฆด๋ ๋ทฐ๋ฅผ ์๋ก ๋ ๋๋งํด์ ๊ธฐ์กด์ ์ด๋ฏธ์ง ํ์ผ๊ณผ ๋น๊ตํ๋ ๋ฐฉ์์ผ๋ก ๋ทฐ ํด๋์ค(UIView, UIViewController)๋ฅผ ๊ฒ์ฌํ ์ ์๋ค. ๋ง์ฝ์ ์ฝ๋ ์์ ์ผ๋ก ์ธํด ๋ทฐ๊ฐ ๋ฐ๋๋ค๋ฉด ํ ์คํธ ์ผ์ด์ค๊ฐ ์คํจํ๊ธฐ ๋๋ฌธ์ ๋ทฐ๊ฐ ์๋ชป๋ ๊ฑธ ๋ฏธ๋ฆฌ ์์๋ผ ์ ์๋ค. ์ค๋ ์ท ํ ์คํธ๋ ์ ๋ ํ ์คํธ๊ธฐ ๋๋ฌธ์ ์ปค๋ฒ๋ฆฌ์ง์ ํฌํจ๋๋๊ฒ ์ฅ์ ์ด๋ค. ์ค๋ ์ท ํ ์คํธ๋ฅผ ์ถ๊ฐํด์ Riblet์ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ๋๋ต 12~15% ์ ๋ ์ฆ๊ฐ์ํฌ ์ ์์๋ค.
Tags: uber/ribs, unit tests
Making a uber/RIBs Unit Test
RIBs is a mobile architecture framework released as an open source developed by Uber. RIBs framework separates the complex app status management and business logic into RIBs chunks(referred to as Riblets) and links them in a tree structure. The object which composes one unit riblet, and the role of each are as follows.
RIBs and Object Oriented Programming
Just like the above diagram each RIBs object has a distinctly separate role. (single responsibility) And the input and output of each object as indicated by an arrow are abstracted with a protocol(dependency inversion), so they can be separated and independently tested using the mocking method. Also, the parent and child riblets are decoupled into a tree structure, so they can be a child riblet that can be newly created or modified as it minimizes code modification of the parent riblets. (Open-closed Principle) What I felt after working on the project for about 2 months is that if you use the RIBs structure, the three principles, SRP, OCP, and DIP, which take up a large part in SOLID are semi-forced to keep. Of course, theyโre easy to break as well. No matter what architecture you use, it depends on how the developer writes the codes, and just introducing an architecture doesnโt improve my codes.
What should be tested
The objects that make up the RIBS architecture are very easy to unit test. The roles are clear and finely divided, and since the parent and child riblets are loosely coupled, high coverage can be achieved. At least 4+ test classes are needed for each riblet, and if you use Xcodeโs code generation template, it automatically generates unit test classes as well as boilerplate codes that are cumbersome to make every time. When I was first working on the project, I was lost because I did not know what to fill in for these many test classes. But as I received code reviews and looked through the previous codes, I found a rule and learned how to test according to the roles and purpose of each class.
Router Test: Child Riblet Routing
A Routerโs test needs to check the action where the child riblet is attached(attachChild) and detached(detachChild) according to business logic. If you look at the Router object description, thereโs an annotation that says โwhen a router creates a child router, a helper builder must be used.โ(link). This means when generating a child Riblet, you shouldnโt directly use the xxBuilder class, but abstract it using xxBuildable and get it injected. This way we can check if the router generated the child riblet properly by injecting xxBuildableMock in a test environment. The Routerโs role is to do routing instead according to the interactorโs request, itโs desirable that thereโs no other logic than this.
Interactor Test: Various Business Logics
The Interactor is in charge of an appโs business logic, so itโs more complicated compared to other classes and gives more freedom to developers. Although itโs not easy to categorize as other components, there are a few that are being used often.
An Interactor uses the reactive programming method in order to deliver information to the child interactor(Reference: official document). If you use the reactive stream, the parent and child interactor wonโt have to engage in direct coupling. It depends on the developer how to implement the stream, but itโs usually good to use RxSwift. RIBs architecture also uses Rx, so if you use this, you donโt necessarily have to add several reactive libraries. You can check if the interactor is exporting a value properly by mocking the stream in a testing environment. On the other hand, if you have to subscribe to the stream injected by the parent interactor to process tasks, inject a mock stream and check whether it processes well according to the data as you change the values in the test case.
-
The interactor receives a user input from view. To check if an appropriate task has been executed according to the user input, directly call the method in the interactor related to the view listener from the test case and check the mocked dependency object to see if the necessary task has been executed.
-
The Interactor calls the router and the presenter(or view) frequently. The view can be attached and detached, and the UI may get updated. So check whether the router and the presenter method are called according to the intent. In this case, rather than simply checking if it got called or not, itโs better to check exactly how many times it was called. You can check how many UI updates are being done unnecessarily, and also check if the view routing is not done in duplication. (Reference: Official tutorial Mock Object)
Builder Test: Generating and Injecting a Concrete Class
The Builder generates a RIBs object to connect the reference points, and creates the concrete classes necessary for dependency injection. Therefore, check the action by using is or as? to see if a correct class type has been generated.
Presenter Test: View Model Generation Logic
Check if the data model has been converted into a view model. Itโs recommended that the data model does not use the UIKit class, and keep the UIKit, Core Graphics type, etc. necessary for configuring a view within the view model.
For example, a data model has a color value as a hex string and the presenter converts it to UIColor. Also, the data model has the date in Date type, and the presenter converts the value into a string using the DateFormatter and saves it into the view model. The conversion logic like this need to be checked.
View Test: Rendering Views
For views, itโs difficult to do meaningful testing only with codes. If you use a snapshot test library, the view is rendered and saved as an image file. You can check the view class(UIView, UIViewController) by rendering the view again and comparing it with the existing image file during testing. If the view changes due to modification in codes, the test case fails, and you can find out that the view is wrong in advance. Since the snapshot test is also a unit test, it gets included in the coverage. By adding snapshot tests, the coverage of the riblet could be increased by approximately 12-15%.
Tags: uber/ribs, unit tests