κΈ°κ° : 2024.04.08 ~. 2024.05.20 / 7μ£Ό
κΈ°κ΄ : SSAFY 10κΈ° 2νκΈ° μμ¨ νλ‘μ νΈ / 6μΈ
Github : https://github.com/LightSwitch-S202/LightSwitch
μ±κ³Ό: SSAFY 10κΈ° 2νκΈ° μμ¨ νλ‘μ νΈ μ°μμ
μννΈμ¨μ΄ κ°λ°μμ νΉμ κΈ°λ₯μ΄λ μ½λλ₯Ό 쑰건μ μΌλ‘ νμ±ννκ±°λ λΉνμ±νν μ μκ² ν΄μ£Όλ κΈ°μ
νΌμ² νλκ·Έ, νΌμ² ν κΈ, κΈ°λ₯ ν κΈ λ± λ€μν μ΄λ¦μΌλ‘ λΆλ¦¬μ°λ©°, μλ‘μ΄ μ½λλ₯Ό λ°°ν¬νμ§ μκ³ λ°νμμ νΉμ κΈ°λ₯μ ν€κ±°λ λ μ μλ€.
μ΄λ₯Ό νμ©νλ©΄ μμ½κ² μ€λ₯μ λμ²νκ±°λ, λ‘€λ°±, A/B ν μ€νΈ, 카리λ λ°°ν¬ λ±μ΄ κ°λ₯νλ€.
Light Switchλ μ€νμμ€ νΌμ³νλκΉ μ루μ μΌλ‘ Docker Image ννλ‘ λ°°ν¬λμ΄ μμΌλ©° MIT λΌμ΄μΌμ€μ λ°λΌ μ¬μ©μ λ§μΆ€ν κ°λ°μ΄ κ°λ₯νλ€.
νΉμ κΈ°λ₯μ ν€κ³ λ μ μλ κΈ°λ₯ ν κΈ, μ€μ¬μ©μλ₯Ό λμ‘°κ΅°κ³Ό μ€νκ΅°μΌλ‘ λλμ΄ μ΄λ€ κΈ°λ₯μ΄ λ ν¨μ¨μ μΈμ§ ν μ€νΈ ν μ μλ A/B Test, νΉμ ν μ¬μ©μλ§ νκ²ν μ΄ κ°λ₯ν Target Testλ₯Ό μ§μνλ€.
λν, νμ©μ λ°λΌ μ€λ₯μ μ½κ² λμ²ν μ μκ³ , κΈ°λ₯ λ‘€λ°±, 카리λ λ°°ν¬λ₯Ό μνν μ μλ€.
Light Switchλ Java, JavaScript, Python μΈμ΄μ SDKλ₯Ό μ§μνλ©°, νλκ·Έλ₯Ό κ΄λ¦¬ν μ μλ μΉ λμ보λλ₯Ό μ€μ¬μΌλ‘ κΈ°λ₯λ§ λΉ λ₯΄κ² ν κΈν μ μλ IOS, Android μ± λμ보λλ₯Ό μ§μνλ€.
- Back-end
- Spring Boot, Kotlin, Spring AOP, SpringJPA, QueryDSL
- JavaSDK
- Java11, SSE(Server-Sent-Event)
- JAVA SDK μ μ
- Flag κ°μ μΊμλ₯Ό ν΅ν΄ λ°ννμ¬ μλ΅μλ κ°μ
- HttpURLConnectionμ μ΄μ©ν Get, Post μλ² ν΅μ
- HttpURLConnectionμ μ΄μ©ν SSE(Server-Sent-Event) ꡬνμΌλ‘ μ€μκ° Flag μ λ°μ΄νΈ μ 보 λ°μ
- Maven Central Repositoryμ Artifacts λ°°ν¬
- Back-end(Kotlin)
- Spring AOPλ₯Ό μ΄μ©νμ¬ Flagμ κΈ°λ‘ μ μ₯
- Front-end
- Tag UI/κΈ°λ₯ ꡬν
- INFRA
- Blue/Green 무μ€λ¨ λ°°ν¬ μ€ν¬λ¦½νΈ μμ±
μ€μ§μ μΌλ‘ LightSwitch νΌμ²νλκΉ
μ루μ
μ μ 곡λ°λ μ¬μ©μλ SDKλ₯Ό μ¬μ©νλ κ°λ°μμΈ Clinet Developerμ΄λ€.
Clinet Developerλ Light Switch SDKλ₯Ό μ΄μ©νμ¬ κ°λ° μμ λ©μλλ‘ μμ½κ² νΌμ² νλκ·Έλ₯Ό μ€μ νκ³ κ΄λ¦¬ν μ μλ€.
Java SDKλ₯Ό Maven Central Repositoryμ λ°°ν¬νμ¬ μ¬μ΄ μ κ·Όμ±μ μ μ§νμλ€.
https://central.sonatype.com/artifact/kr.lightswitch.www/lightswitch
μ£Όμ μ¬μ© λ°©λ²μμλ λνμ μΈ μΈμ΄μΈ Java SDKλ₯Ό κΈ°μ€μΌλ‘ μ€λͺ νλ©°, κ° μΈμ΄μ λν μ€λͺ μ μΈμ΄λ³ SDK μ μ₯μμ README.mdμμ μ½μ μ μλ€.
build.gradle
implementation 'kr.lightswitch.www:lightswitch:1.0.1'build.gradle.kt
implementation("kr.lightswitch.www:lightswitch:1.0.0")pom.xml
<dependency>
<groupId>kr.lightswitch.www</groupId>
<artifactId>lightswitch</artifactId>
<version>1.0.0</version>
</dependency>
μμ‘΄μ±μ μΆκ°νλ©΄ ν΄λΉ SDKλ₯Ό μ΄μ©νμ¬ νΌμ²νλκ·Έμ κ΄λ ¨ν λ©μλ νΈμΆμ΄ κ°λ₯νλ€.
LightSwitchλ νμ κ°μ κ°μ νλκ·Έλ₯Ό μ»μ΄μ€κΈ° μν΄, μ±κΈν€ μΈμ€ν΄μ€λ₯Ό μ¬μ©νλ€. LightSwitch μΈν°νμ΄μ€μ getInstance()λ₯Ό νΈμΆνμ¬ μ±κΈν€ μΈμ€ν΄μ€λ₯Ό μμ±ν μ μλ€.
μ¬μ© μμ :
LightSwitch lightSwitch = LightSwitch.getInstance();LightSwitch μλ²μμ μ΄κΈ° νλκ·Έλ₯Ό SDK λ΄λΆμ μΌλ‘ μΊμ±νκΈ°μν΄ νμμ μΌλ‘ LightSwitch μλΉμ€λ₯Ό μ΄κΈ°ν ν΄μΌ νλ€.
init() μν μ μ
λ ₯λ°λ configλ μλμ κ°λ€.
// @param sdkKey : LightSwitch μλ²μμ λ°κΈ λ°μ sdk Key
// @param serverUrl : μ°κ²°ν LightSwitch μλ² URL
void init(String sdkKey, String serverUrl);μ¬μ© μμ :
lightSwitch.init("your-private-sdk-key","https://lightswitch.kr");νλκ·Έμμ λ°ν κ°μ λ°μμ€κΈ° μν΄μ ν΄λΌμ΄μΈνΈ μ μ μ κΈ°λ³Έμ μΈ μ 보λ₯Ό μ 곡νλ LSUser.classλ₯Ό μ μΈν΄μΌ νλ€.
LSUser.classλ Builder ν¨ν΄λ§μ μ§μνλ©°, userIdλ νμ κ°μΌλ‘ μ§μ ν΄μΌ νλ€.
λν, μ μ λ³λ‘ μμ±μ μ§μ νμ¬ νΉμ μ μ λ§μ λμμΌλ‘ ν νκ² ν
μ€ν
μ μνν μ μλ€.
μ¬μ© μμ :
LSUser lsUser = new LSUser.Builder("userId")
.build();
LSUser lsUser = new LSUser.Builder("123")
.property("name", "LightSwitch") // μμ± : κ°
.property("address", "seoul") // ν€μλμ λ§€μΉλλ€.
.build();νλκ·Έμ λ°ν κ°μ μ»λ λ©μλλ νμ μμ μ±μ 보μ₯νλ λ©μλμ 보μ₯νμ§ μλ λ©μλλ‘ λλμ΄μ§λ€.
// νμ
μ μ μ μμ
<T> T getFlag(String key, LSUser LSUser, Object defaultValue);
// νμ
μμ μ± λ³΄μ₯
Boolean getBooleanFlag(String key, LSUser LSUser, Boolean defaultValue);
Integer getNumberFlag(String key, LSUser LSUser, Integer defaultValue);
String getStringFlag(String key, LSUser LSUser, String defaultValue);getFlag()λ νμ
μμ μ±μ 보μ₯νμ§ μλλ€.
λ§μ½ λ°ν λ°μ νλκ·Έμ λ°ν κ° νμ
μ΄ μΌμΉνμ§ μλλ€λ©΄, Java.lang.ClassCastException μμΈλ₯Ό λμ§κΈ° λλ¬Έμ try/catchλ₯Ό μ μ ν μνν΄μΌ νλ€.
νμ
μμ μ±μ 보μ₯νλ λ©μλλ‘ νλκ·Έλ₯Ό μ»μ΄μ¬ λ, ν΄λΉ νλκ·Έμ νμ
κ³Ό λ©μλ μ νμ΄ μΌμΉνμ§ μλλ€λ©΄ defaultValueλ₯Ό λ°ννλ€.
λν, νλκ·Έμ keyμ μΌμΉνλ μ΄λ¦μ νλκ·Έκ° μμ κ²½μ°μλ defaultValueλ₯Ό λ°ννλ€.
μ¬μ© μμ :
boolean typeUnsafeFlag = lightswitch.getFlag("flag-name", user, false);
boolean typeSafeFlag = lightswitch.getBooleanFlag("flag-name", user, false);LightSwitch μλΉμ€μμ μ°κ²°μ λ°νμμ ν΄μ νκ³ μΆμ κ²½μ° destroy()λ₯Ό μ΄μ©νμ¬ μ°κ²°μ ν΄μ ν μ μλ€.
μΊμ±λ λͺ¨λ νλκ·Έλ μ΄κΈ°ν λ¨μ μ μν΄μΌνλ€.
destroy()κ° νΈμΆ λ κ²½μ° λ€μκ³Ό κ°μ μμ
λ€μ΄ μ΄λ£¨μ΄ μ§λ€.
- Lightswitch Connection μ°κ²° ν΄μ
- Lightswitch SSE μ°κ²° ν΄μ
- μΊμ±λ νλκ·Έ μ΄κΈ°ν
void destroy();μ¬μ© μμ :
lightSwitch.destroy();LightSwitch SDKλ λ΄λΆμ μΌλ‘ SSE(Server Sent Event) ν΅μ μ ν΅ν΄ μλ²λ‘λΆν° νλκ·Έ λ³κ²½μ¬νμ μ€μκ°μΌλ‘ κ°μ§νλ€.
λ°λΌμ, μ¬μ©μλ λ³κ²½λ νλκ·Έλ₯Ό μλ‘ λ°μμ€κΈ° μν΄μ μλ¬΄λ° μμ
λ ν νμκ° μλ€.
LightSwitchλ νλκ·Έμ ν€μλμ μμ±μ ν΅ν΄ νκ² ν μ€ν μ μ§μνλ€.
λ¨Όμ , μΉ λμ보λλ₯Ό ν΅ν΄ νλκ·Έμ ν€μλλ₯Ό μ€μ ν μ μλ€.
νλμ ν€μλμλ νλμ λ°ν κ°μ΄ μμΌλ©°, ν¬ν¨λ μμ±μ λͺ¨λ λ§μ‘±ν΄μΌ ν€μλ 쑰건μ λ§μ‘±ν κ²μΌλ‘ κ°μ£Όνλ€.
ν€μλ μ€μ μ΄ μλ£ λλ€λ©΄, νλκ·Έλ₯Ό μ»μ΄μ¬ λ LSUser.classμ μμ±[μμ±:κ°]μ μ€μ ν μ μλ€.
LSUser.classμ μμ±[μμ±:κ°]μ΄ ν€μλμ ν¬ν¨λ μμ±μ λͺ¨λ ν¬ν¨λμ΄ μμ κ²½μ°, ν€μλ λ°ν κ°μ λ°ννλ€.
ν€μλμ ν¬ν¨λ μμ±μ μΌλΆλ§ ν¬ν¨νκ³ μκ±°λ ν¬ν¨νμ§ μλ κ²½μ°, νλκ·Έ λ³μ λΉμ¨λ°©λ²μ λ°λΌ κ°μ λ°ννλ€.
μ¬μ© μμ :
LSUser lsUser = new LSUser.Builder("123")
.property("name", "olrlobt")
.property("age", "27")
.build();
Boolean flagTest = lightSwitch.getBooleanFlag("FLAG TEST", lsUser, false);μ μμμμ νλκ·Έμ ν€μλ μμ±μ΄ [name : olrlobt]μ [age : 27]λ₯Ό λͺ¨λ λ§μ‘±νλ©΄ ν€μλ λ°ν κ°μ λ°ννλ€.
λν νλκ·Έμ ν€μλ μμ±μ΄ [name : olrlobt]μ [age : 27]λ₯Ό λ μ€ νλλ§ λ§μ‘±ν΄λ ν€μλ λ°ν κ°μ λ°ννλ€.\
νμ§λ§, νλκ·Έμ ν€μλ μμ±μ΄ [name : olrlobt]μ [age : 27] μΈμλ [company : ssafy]λ₯Ό κ°κ³ μλ€λ©΄, ν€μλ λ°ν κ°μ λ°ννμ§ μκ³
νλκ·Έ λ³μ λΉμ¨ λ°©λ²μ λ°λΌ λ°ν κ°μ λ°ννλ€.
LightSwitchλ νλκ·Έ λ³μ λΉμ¨μ μ€μκ°μΌλ‘ μ‘°μ νλ©° λ€μνκ² νμ©ν μ μλ€.
λ³μ λΉμ¨μ νλκ·Έλ₯Ό μ»μ λ μ¬μ©νλ LSUser.classμ νμ userIdμ λ°λΌ ν΄μ(MD-5)κ° λ°±λΆμ¨μ κΈ°λ°μΌλ‘ νλ€.
μ¬μ© μμ :
// Flag Variation 1 : True : 50%
// Flag Variation 2 : False : 50%
LSUser lsUser = new LSUser.Builder("000") // User Hash : 54.72%
.build();
LSUser lsUser2 = new LSUser.Builder("123") // User Hash : 03.17%
.build();
Boolean flagTest = lightSwitch.getBooleanFlag("FLAG TEST", lsUser, false); // False
Boolean flagTest2 = lightSwitch.getBooleanFlag("FLAG TEST", lsUser2, false); // Trueμ μμμμ νλκ·Έμ λ³μ λΉμ¨μ΄ 50%, 50%μ λΉμ¨λ‘ μ€μ μ΄ λμ΄ μλ€λ©΄, LSUser.classμ userId κ°μμ ν΄μκ°μ λ°λΌ λ°ν κ°μ΄ λ¬λΌμ§ μ μλ€.
μ΄λ₯Ό μ μ ν νμ©νμ¬, A/B ν
μ€νΈ, μΉ΄λ리 λ°°ν¬ λ± μ¬λ¬ λ°©λ©΄μΌλ‘ νμ©ν μ μλ€.
Java SDKλ₯Ό ν¬ν¨ν μΈλΆ νλ‘μ νΈ( μΆκ΅¬ νλ«νΌ νλ‘μ νΈ : K리λ²μ€)μμ νΌμ²νλκΉ μ루μ μ΄ μ μμ μΌλ‘ μλνλμ§ νμΈνμλ€.
νλκ·Έμ μ€μ ν λΉμ¨μ κΈ°λ°μΌλ‘ κ°μ λ°ννλ€. μ΄λ₯Ό A/B ν μ€ν μ νμ©ν μ μλ€.
-
Hash μκ³ λ¦¬μ¦ MD5(Message Digest Algorithm 5)λ₯Ό μ¬μ©νμ¬ νμμ κ³ μ λ°±λΆμ¨ κ°μΌλ‘ λλλ€. μ΄μ λ°λΌ νμμ κ³ μ λ²νΈμ λ°λΌ λΉμ¨μ΄ λλκ² λλ€.
-
Java, JavaScript, Python λͺ¨λ κ°μ λ°©μμΌλ‘ ꡬννμ¬ μΌκ΄μ±μ μ μ§νμλ€.
-
SSE ν΅μ μ νμ©νμ¬ Flagκ° μμ±, μμ , μμ λ λ μ€μκ°μΌλ‘ μλ΅μ λ°μ νλκ·Έλ₯Ό μ μ©νλ€.
-
μμ°μ μ¬μ©ν μ½λ
LSUser lsUser = new LSUser.Builder(userId)
.build();
if (lightSwitch.getBooleanFlag("Event κ΄κ³ νλκ·Έ", lsUser, false)) {
eventResDtos.sort(Comparator.comparing(CheckEventResDto::getEnd_date));
}첫 νλ©΄μμλ μ μ λ³λ‘ κ΄κ³ λ°°λμ μμκ° μμμΌ μμΌλ‘ λμΌνμ§λ§, νλκ·Έκ° νμ±ν λκ³ λλ©΄ μ’μΈ‘ νμμ κ΄κ³ μμκ° λ§κ°μΌ μμΌλ‘ λ°λλ κ²μ νμΈν μ μλ€.
Userμ κ³ μ μμ΄λμ λ°λΌ λΉμ¨μ΄ κ³μ°λκ³ , 50% λΉμ¨μ μνλ μ μ λ€μ λ§κ°μΌ μμΌλ‘ κ΄κ³ λ₯Ό μ 곡λ°λλ€.
νλκ·Έμ μ€μ ν νΉμ ν€μλλ₯Ό κΈ°λ°μΌλ‘ κ°μ λ°ννλ€. νλκ·Έλ₯Ό μ¬μ©ν λ, Userμ κ³ μ μμ±λ€μ μ€μ ν μ μμΌλ©°, μ΄ κ²μ΄ ν€μλμ μΌμΉνλ€λ©΄ κ°μ λ°ννλ€. νΉμ μ μ νκ²ν ν μ€νΈμ νμ©ν μ μλ€.
-
Builder ν¨ν΄μ μ¬μ©νμ¬ νμμ μμ±μ κ°νΈνκ² μ§μ ν μμλ€.
-
SSE ν΅μ μ νμ©νμ¬ Flagκ° μμ±, μμ , μμ λ λ μ€μκ°μΌλ‘ μλ΅μ λ°μ νλκ·Έλ₯Ό μ μ©νλ€.
-
μμ°μ μ¬μ©ν μ½λ
LSUser lsUser = new LSUser.Builder(userId)
.property("mainBadge", user.getBadge())
.build();Userμ Badgeμ κ°μ κ°μ Έμ μμ±μΌλ‘ μ€μ μ ν΄ μ£Όμλ€. μ΄ κ²μ΄ νλκ·Έμ ν€μλμ λμ‘°λμ΄, μμ°μμ Nullμ΄ μλ μ μ λ€μ μ€λ¬Έμ‘°μ¬ λ°°λκ° μλ¨μΌλ‘ μ΄λνλ κ²μ νμΈν μ μλ€.
- Spring AOPλ₯Ό μ΄μ©νμ¬, Flagμ λ³λμ¬νμ κ°μ§νμ¬ κΈ°λ‘νλ€.








