2022년 9월 12일 월요일

Android Graphql

Android Graphql

Android 와 GraphQL

모바일 앱 개발자라면 더 빠른 API와 더 빠른 애플리케이션의 중요성을 이해해야 합니다. 특히 데이터 트래픽이 더 비싼 국가에서는 필요한 데이터만 얻는 것이 특히 중요합니다.

그러나 API는 각기 다른 요구 사항을 가진 다양한 클라이언트를 위해 설계되었으며 대부분의 경우 이러한 API는 모바일 애플리케이션에 최적화되어 있지 않습니다. 이것이 GraphQL과 같은 새로운 기술이 도움이 될 수 있는 곳입니다.

GraphQL은 클라이언트가 필요한 데이터를 정확히 얻을 수 있도록 하는 API용 쿼리 언어입니다. 아직 GraphQL을 모르신다면 아래 링크에서 자세히 읽어보실 수 있습니다.graphql.org

GraphQL

GraphQL은 2012년 Facebook에서 내부적으로 개발한 데이터 쿼리 언어입니다. 2015년에 오픈 소스로 공개되었으며 RESTful 아키텍처의 대안을 제공하는 것을 목표로 합니다. GraphQL은 Graph+QL이라고 볼 수 있습니다. 여기서 QL은 쿼리 언어.그래서 GraphQL은 시각적(그래픽) 쿼리 언어를 의미하며, 이는 클라이언트가 서버에서 데이터를 요청하는 방법을 설명하는 API 구문입니다

기존의 API설계는 각 Endpoint와 해당메소드,파라메터를 바탕으로 작업했습니다. 이렇게 했을때 문제점은 너무 많은 각각의 Endpoint가 생성되고 새로운 디바이스등에서는 비슷한 엔드포인트를 서버에 새로 구축해야 하는 경우가 있었습니다.

GraphQL은 이를 하나의 엔드포인트로 결집하고 API 에 Query의 개념을 도입하여 데이터베이스의 쿼리처럼 원하는 데이터만 API로부터 받을수 있도록 하여 쓸데없는 트래픽을 줄여줄수있게 해줍니다.

GraphQL과 같이 등장하는 것중에 BFF(Backend For Frontend)라는 말이 등장하는데, BFF구현을 위해서 GraphQL을 도입하여 마이크로서비스로 구현된 백엔드의 각 서버에서 취합된 정보를 프론트엔드에 한번에 전달 하는 것을 가능케 합니다.

GraphQL 구성

  • sheme
    • 프로토콜, 사양 또는 인터페이스 문서로 간주 될 수 있습니다.
  • 데이터유형
    • 친숙한 JavaScript 또는 TypeScript String와 유사하게 Int(정수), Float(부동 소수점), String(문자열), Boolean(부울 유형) 및 ID(고유 식별자 유형). 동시에 날짜 유형과 같은 스칼라 유형을 사용자 정의하고 관련 직렬화, 역직렬화 및 유효성 검사 기능을 구현할 수도 있습니다
  • 3개의 메서드가 있습니다.
    • query : 데이터를 조회할때 사용합니다.
    • mutation : 데이터를 저장/가공 할때 사용합니다.
    • subscription : 데이터에 변화가 있을 때 이를 감지해서 클라이언트에 통보하여 줄때 사용합니다.
  • 이밖에 Enumeration types(열거형 유형), Union types(공집합 유형)이 있습니다. 동시에 코드를 더 잘 재사용하기 위해 (인터페이스) 기능도 제공됩니다

코드의 재사용성을 높이는 fragment

쿼리문에 작성하는 코드 부분의 재사용성을 높이기 위해 (fragment), (inline fragment) 및 (instruction) 함수 GraphQL도 제공됩니다 . 처음 두 가지는 JavaScript 의 (function) 및 (anonymous function )과 비교할 수 있으며, (instruction)은 우리가 전달하는 매개변수에 따라 특정 필드를 반환해야 하는지 여부를 결정할 수 있습니다.

Android에서 GraphQL

Android 플랫폼에서 GraphQL API를 사용하는 방법을 살펴보겠습니다. 인터넷에 네트워크 요청을 위한 많은 타사 라이브러리가 있지만 Android 플랫폼에는 아직까지 GraphQL 라이브러리가 Apollo 하나뿐입니다.

두 가지 주요 구성 요소가 있습니다.

  • Appllo Codegen : 이 구성 요소는 ButterKnife와 같은 코드를 생성하는 gradle 플러그인이며, apollo codegen은 표준 graphql 쿼리에서 컴파일 타임에 Java 모델을 생성합니다.
  • 네트워킹 및 캐싱(네트워킹/캐싱) : Apollo Android의 또 다른 구성 요소는 GraphQL API와의 모든 네트워크 통신을 담당하는 네트워크 및 캐시 부분으로, 반환된 데이터를 올바른 모델로 구문 분석하여 GraphQL 쿼리 및 응답 캐시에 동적 데이터를 전달할 수 있습니다.

Apollo 설치

Apollo는 GraphQL의 프론트엔드/백엔드 라이브러리입니다.
enter image description here

각각 Apollo Server, Apollo Client이고 제목 그대로 백엔드/프론트엔드용 프로그램입니다. brew로 설치하거나 npm 으로 도 설치가능합니다.

brew install apollo-cli

시작하기 Helloworld

1. 프로젝트에 GraphQL 라이브러리 등록

새로운 Android 프로젝트를 만듭니다.
프로젝트의 build.gradle에 classpath를 추가합니다.

com.apollographql.apollo3:apollo-gradle-plugin:3.1.0

앱의 build.gradle는

// plug ins 
plugins {
 ... 
id("com.apollographql.apollo3")
}
  
android {
    // src/main/com/sample/graphql 로 대충넣어도 됩니다.
	apollo {  
	  packageName.set("com.sample.graphql")  
	}
}  
dependencies {
  implementation("com.apollographql.apollo3:apollo-runtime:3.1.0")  
  implementation("com.apollographql.apollo3:apollo-adapters:3.1.0")  
}

2. 로컬에서 프로그래밍 할수 있도록 샘플 스키마를 가져오기

아폴로가 설치되었기 때문에 아래 명령어로 외부의 스키마를 가져올수가 있습니다. GraphQl 을 이용해서 스키마에 정의된 대로 자료를 가져오거나 넣을수있습니다.

./gradlew :app:downloadApolloSchema --endpoint=https://apollo-fullstack-tutorial.herokuapp.com/graphql --schema=`pwd`/app/src/main/graphsql/com/sample/graphql/scheme.graphqls

apollo의 샘플 데이터를 받았다면, 샘플데이터서버에 가서 스키마의 상세 스펙을 확인할수 있습니다.
enter image description here
화면의 Reference 를 눌러서 Query 목록중 launches 항목의 [>] 버튼을 눌러서 Explorer 화면으로 이동합니다.
enter image description here
Documentation과 Operation항목이 있습니다.
순서대로

  • launches : LaunchConnection +
  • Fields > cursor, hasMore, launches 선택
  • launches의 Field중 id,site 선택
    [ > Launches ] 를 선택하면 목록이 나타납니다.
    이렇게 원하는 필드를 원하는 쿼리에 추가하여 데이터를 전송받을 수 있습니다.
  • […] 메뉴의 copy operation을 선택하고
    Android Studio의 graphql폴더에 LaunchList.graphql파일을 저장합니다.
  • 안드로이드를 재빌드 하면 build/source/apollo/service/LaunchesQuesry.kt 파일이 생성되어있고 자동으로 클래스및 데이터 클래스가 생성되어 있습니다.
  • 적당한 곳에 Apollo.kt파일을 만듭니다.
import com.apollographql.apollo3.ApolloClient    
val apolloClient = ApolloClient.Builder().serverUrl(  
    serverUrl = "https://apollo-fullstack-tutorial.herokuapp.com/graphql"  
).build()
  • Fragment나 액티비티의 적당한 곳, 혹은 코루틴으로 데이터를 가져와 봅니다.
  
lifecycleScope.launchWhenCreated{  
  val response = apolloClient.query(LaunchesQuery()).execute()  
    Log.v("Launch", "${response.data}")  
  
}
/*
Data(launches=Launches(cursor=1584533760, hasMore=true, launches=[Launch(id=110, site=CCAFS SLC 40), Launch(id=110, site=KSC LC 39A), Launch(id=109, site=CCAFS SLC 40), Launch(id=108, site=VAFB SLC 4E), Launch(id=107, site=KSC LC 39A), Launch(id=106, site=CCAFS SLC 40), Launch(id=105, site=CCAFS SLC 40), Launch(id=104, site=KSC LC 39A), Launch(id=103, site=KSC LC 39A), Launch(id=102, site=KSC LC 39A), Launch(id=101, site=CCAFS SLC 40), Launch(id=100, site=CCAFS SLC 40), Launch(id=99, site=KSC LC 39A), Launch(id=98, site=CCAFS SLC 40), Launch(id=97, site=CCAFS SLC 40), Launch(id=96, site=CCAFS SLC 40), Launch(id=95, site=CCAFS SLC 40), Launch(id=94, site=KSC LC 39A), Launch(id=93, site=KSC LC 39A), Launch(id=92, site=KSC LC 39A)]))*/

.graphqlconfig 파일을 작성하여 서버로부터 스키마파일을 쉽게 가져올수 있습니다.
또한 Android Studio의 GraphQL Plugin 을 설치했다면, GraphQL판넬에서 Endpoints목록에서 해당 주소를 더블클릭하여 스키마를 다운로드 받을수 있습니다.

{  
  "name": "Untitled GraphQL Schema",  
  "schemaPath": "schema.graphqls",  
  "extensions": {  
    "endpoints": {  
      "Default GraphQL Endpoint": {  
        "url": "https://apollo-fullstack-tutorial.herokuapp.com/graphql",  
        "headers": {  
          "user-agent": "JS GraphQL"  
  },  
        "introspect": true  
  }  
    }  
  }  
}

부록

BFF (Backend For Frontend) & GraphQL

Backend For Frontend는 이름에서 알 수 있듯이 프런트 엔드 요구에 구체적으로 응답하는 서버입니다.
Backend For Frontend 는 2015/16년에 점차 주목을 받기 시작했습니다.수년간의 연습과 개발을 통해 업계는 이미 다양한 기술 스택의 솔루션과 사례를 많이 보유하고 있습니다. 개인적으로 BFF를 정확히 이해하기 위해서는 구체적인 기술적인 내용과 비즈니스 시나리오를 제쳐두고 팀워크의 관점에서 바라볼 필요가 있다.

BFF의 탄생은 서버 측 도메인의 기능 축적과 프런트 엔드의 다양한 데이터 요구 사항 간의 모순을 해결하는 것입니다. 따라서 프런트 엔드와 서버 간의 충돌을 줄이고 개발 비용을 줄이는 데 도움이 되는 모든 것이 BFF 논의 범위에 속합니다.

우리는 다음 측면에서 BFF의 적용 가능한 시나리오를 논의합니다.

  • 데이터 집계 클리핑

    일반적으로 서버 측 개발에 대한 페이지의 영향을 줄이기 위해 서버 측에서 API 계층을 분리하여 페이지 요구 사항에 구체적으로 대응합니다. 그러나 몇 가지 명확한 제약 조건과 지침이 없기 때문에 여러 가지 이유로 일부 비즈니스 로직이 API 계층에 점차 나타나게 되어 API 계층과 비즈니스 계층 간의 경계가 흐려지고 페이지 요구 사항에 대한 응답에 영향을 미칩니다.

  • SSR

    이제 SSR에 관해서는 일반적으로 NodeJS + React/Vue 기반의 서버 측 렌더링으로 이해됩니다. 사실 초창기에는 템플릿 기반의 구현도 있었는데, Apache Velocity (Java), EJS (JS) 등 이 잘 알려진 템플릿 엔진이었습니다.

  • 업로드 다운로드

    업로드 및 다운로드는 비교적 일반적인 비즈니스 시나리오이지만 기능의 일반화로 인해 비즈니스 기능으로 비즈니스 시스템에 담기는 어려운 경우가 많습니다.

  • 인터페이스 포워딩

    일부 시나리오에서는 다른 인터페이스를 집계하지 않고 페이지 요구 사항을 충족할 수 있으며 페이지 요청을 도메인 서비스로 직접 전달할 수 있습니다. 하지만 로그인, 인증 등 일련의 검증 로직이 필요하기 때문에 Nginx를 통해 직접 전달하는 것은 불가능하며, 이를 처리하기 위한 서비스 계층(위에서 언급한 API 계층 등)이 필요하다.

이러한 문제 중 일부는 서버 측 개발에서 더 나은 문제가 있고 일부는 프론트엔드 개발에서 더 나은 것을 볼 수 있습니다. 이것은 또한 BFF 자체의 위치를 ​​확인합니다.

GraphQL

위에서 BFF를 적용할 수 있는 여러 시나리오에 대해 논의했으며 그 중 데이터 집계 및 자르기가 가장 일반적이고 충돌하는 시나리오라고 할 수 있습니다. GraphQL과 그 생태계는 솔루션 세트를 제공합니다.

공식 정의에서 GraphQL은 두 가지 의미를 갖습니다.

  • API용 쿼리 언어입니다.
  • 기존 데이터를 사용하여 이러한 쿼리를 완료하는 런타임입니다.

위의 다이어그램에서 우리는 DSL을 통해 시스템의 유형을 설명합니다. 각 유형은 노드이며 여러 노드와 노드 간의 관계는 결국 그래프를 생성합니다. 모든 쿼리는 이 그래프를 기반으로 합니다. 쿼리할 때마다 그래프의 특정 노드에서 시작하여 트리를 선택한다는 것을 시각적으로 이해할 수 있습니다.

GraphQL에는 다음과 같은 특징이 있습니다.

  • 더도 말고 덜도 말고 온디맨드 방식으로 데이터를 반환하고 데이터는 매우 결정적입니다.
    • 필요한 데이터 필드, 중첩 구조 등은 끝에 설명하고 서버는 설명에 따라 반환합니다. 동시에 필드 유형과 필드가 비어 있는지 여부에 대한 명확한 규칙이 있습니다.
  • 병합 요청
    • 페이지의 여러 데이터 요청을 단일 GraphQL 쿼리로 결합할 수 있습니다. 약한 네트워크 환경에서 특히 유용합니다.
  • 잘 정립된 유형 시스템
    • 시스템의 모든 모델은 필드 유형, 모델 구조 및 모델 간의 관계를 포함하여 스키마를 통해 설명됩니다. 인트로스펙션 모드를 통해 시스템의 모든 스키마 정보를 조회할 수 있으며, 그 효과는 API 문서와 동일하다.
    • 유형 시스템 주변에서 클라이언트 측 코드 생성 도구 GraphQL 코드 생성기와 같은 많은 효율적인 도구를 개발할 수 있습니다.
  • 부드러운 API 업그레이드, 버전 없음
    • 필드를 추가해도 기존 쿼리에는 영향을 미치지 않습니다.
    • Mark Deprecated 필드는 인트로스펙션 모드에서 자동으로 표시됩니다.
  • 세밀한 필드 제어
    • 적절한 Resolver를 정의하면 시스템의 모든 모델과 필드를 세밀하게 제어할 수 있습니다. 유형 변환, 묻힌 지점 모니터링 등

2022년 9월 11일 일요일

Kotlin DSL

Kotlin DSL

Kotlin DSL

Kotlin은 DSL 친화적인 언어이며 다양한 구문 기능을 통해 DSL(domain-specific languages (DSLs))을 구축하고 특정 시나리오에서 코드의 가독성과 보안을 개선할 수 있습니다.

1. DSL이란?

domain-specific languages, 즉 도메인 특정 언어이다. 도메인이라는 지정된 영역에 맞게 정의해서 사용하는 언어라는 의미이다.
일반 범용언어랑 다르게 SQL처럼 특정 작업을 할때 사용하는 언어의 종류이다.
시스템 구축 초기에 사용자와 빌더의 언어 모델이 일치하지 않아 요구사항 수집이 어려운 문제를 해결하기 위함입니다.
도메인전문가와 개발간의 공통된 언어와 개체,규칙을 사용할수 있도록 해줍니다.

Android개발자라면 groovy 언어의 DSL로 괄호와 지정된 문장을 적는 적만으로도 설정값을 지정할수 있다는 것을 알수 있을 것이다.

android {
  compileSdkVersion 28
  defaultConfig {
    applicationId "com.my.app"
    minSdkVersion 24
    targetSdkVersion 30
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
}

Kotlin 도 DSL을 지원하여 프로그래밍시에도 위의 예제처럼 좀더 직관적이고 간단하게 작업할수 있습니다.

DSL과 범용 프로그래밍 언어의 차이점

  • 비 프로그래머용 DSL, 도메인 전문가용
  • DSL에는 데이터 구조와 같은 세부 정보를 포함하지 않는 상위 수준 추상화가 있습니다.
  • DSL은 표현력이 제한적이며 현장의 모델만 설명할 수 있는 반면 범용 프로그래밍 언어는 임의의 모델을 설명할 수 있습니다.

2. Kotlin DSL

Kotlin은 Android의 주요 프로그래밍 언어이므로 Android 개발에서 Kotlin의 DSL을 활용하여 특정 시나리오에서 개발 효율성을 높일 수 있습니다. 예를 들어 Compose의 UI 코드가 좋은 예인데, DSL을 사용하여 Kotlin 코드가 XML과 동일한 표현성을 갖도록 함과 동시에 형식 안전성을 고려하여 UI 개발의 효율성을 높입니다.

Kotin DSL에는 많은 이점이 있음을 알 수 있습니다.

  • XML과 유사한 구조적 표현력
  • 더 적은 수의 문자열, 더 강력한 유형, 더 안전한
  • linearLayoutParams와 같은 객체는 쉽게 재사용할 수 있도록 추출할 수 있습니다.
  • onClick과 같은 이벤트 처리를 레이아웃에 동기적으로 포함
  • 필요한 경우 if 및 for와 같은 제어 문을 포함할 수도 있습니다.

3. Kotlin 에서 DSL 구현 방법

3.0 build.gradle의 depencencies를 kotlin dsl로 흉내내보자.

Dependency 라는 이름의 클래스를 만들자

class Dependency {
    var libs = mutableListOf<String>()
    fun implementation(lib: String) {
        libs.add(lib)
    }
}

고차함수를 만들어서 Dependency의 확장함수(익명람다) 로 만들어 입력된 경로가 클래스변수에 저장하도록 하자.

fun dependencies(block: Dependency.() -> Unit): List<String> {
    val dependency = Dependency()
    dependency.block()
    return dependency.libs
}

dependencies는 마지막 인자가 고차함수를 받을수 있으니까 { } 로 표현가능하다. 그리고 함수내부에서 Dependency()를 선언후 인자로 받은 익명 확장함수를 실행할수 있다.
결국 아래처럼 쓸 수 있다

dependencies {
        implementation("com.jacky.ll")
        implementation("com.jacky.hh")
    }

var list = dependencies {
        implementation("com.jacky.ll")
        implementation("com.jacky.hh")
    }
    for (text in list) {
       println("$text")
    }    

다른 예로 하루전 날짜 가져오기는 다음과 같이

3.1 고차함수는 중괄호 호출로 표현가능

일반적인 DSL은 중괄호를 사용하여 수준을 표현합니다. Kotlin의 고차 함수를 사용하면 람다 유형의 매개변수를 지정할 수 있으며 람다가 매개변수 목록의 끝에 있을 때 괄호를 이스케이프 처리하여 DSL의 중괄호 구문 요구 사항을 충족할 수 있습니다.

중괄호 구문 구현의 핵심은 객체 생성 및 초기화 논리를 꼬리 람다로 고차 함수로 캡슐화하는 것이라는 것을 알고 있습니다.이 아이디어에 따라 다음 코드를 변환합니다.

//정의된 함수
public LinearLayout(Context context, @Nullable AttributeSet attrs) {  
    super((Context)null);  
    throw new RuntimeException("Stub!");  
}

//Kotlin 에서 사용시
LinearLayout(context).apply {
    orientation = LinearLayout.HORIZONTAL
    addView(ImageView(context))
}

가독성을 향상시키기 위해 기본값에 따라 orientation명명 . HorizontalLayout또한 Compose 스타일을 모방하고 대문자를 사용하여 DSL 노드를 더 쉽게 인식할 수 있도록 합니다.

fun HorizontalLayout(context: Context, init: (LinearLayout) -> Unit) : LinearLayout {
    return LinearLayout(context).apply {
        orientation = LinearLayout.HORIZONTAL
        init(this)
    }
}

매개변수 init는 LinearLayout중괄호로 초기화할 수 있도록 방금 만든 개체를 전달하는 후행 람다입니다. ImageView에 대한 유사한 고차 함수 도 정의한 후 호출 효과는 다음과 같습니다.

HorizontalLayout(context) {
    ...
    it.addView(ImageView(context) {
        ...
    })
}

3.2 리시버를 통한 컨텍스트 전달

3.1의 고차 함수에 의해 변환된 DSL에서 중괄호는 의 도움으로 초기화되어야 it하며 addView의 모양은 거의 우아하지 않습니다. 먼저 람다의 매개변수를 Receiver로 변경할 수 있으며 중괄호에 it대한 this직접 다음으로 변경하거나 생략 할 수 있습니다.
리시버의 원리는 익명의 확장함수를 인수로 추가함으로서, 해당 인수에

fun HorizontalLayout(context: Context, init: LinearLayout.() -> Unit) : LinearLayout {
    return LinearLayout(context).apply {
        orientation = LinearLayout.HORIZONTAL
        init()
    }
}

ImageView도 DSL답게 바꾸어 봅니다.

fun ViewGroup.ImageView(init: ImageView.() -> Unit) {
    addView(ImageView(context).apply(init))
}

최종 DSL

HorizontalLayout(context) { // this: LinearLayout
    ...
    ImageView { // this: ImageView
        ...
    }
}

3.3 확장기능 최적화 코드스타일

자주 사용되는 이벤트 리스너의 경우 override fun 가 중복되어 보기 싫을 때가 있습니다. 이를 DSL로 깔끔하게 변경 가능합니다.

TextView의 경우.

TextView {
    addTextChangedListener( object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            ...
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            ...
        }

        override fun afterTextChanged(s: Editable?) {
            ...
        }
    })

TextView다음에 대한 DSL 친화적인 확장 기능 추가 :

fun TextView.textChangedListener(init: _TextWatcher.() -> Unit) {
    val listener = _TextWatcher()
    listener.init()
    addTextChangedListener(listener)
}

class _TextWatcher : android.text.TextWatcher {

    private var _onTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        _onTextChanged?.invoke(s, start, before, count)
    }
    fun onTextChanged(listener: (CharSequence?, Int, Int, Int) -> Unit) {
        _onTextChanged = listener
    }
   
}

DSL 로 다시 작성해 보면 직관적이고 깔끔해졌습니다

Text {
    textChangedListener {
        beforeTextChanged { charSequence, i, i2, i3 ->
            //...
        }
    
        onTextChanged { charSequence, i, i2, i3 ->
            //...
        }
    
        afterTextChanged {
            //...
        }
    }
}

4. DSL 추가최적화

4.1 infix로 가독성 향상

Kotlin의 중위 함수를 사용하면 함수에서 점 및 괄호와 같은 프로그램 기호를 생략할 수 있으므로 명령문이 더 자연스럽고 가독성이 더욱 향상됩니다.
예를 들어 모든 View에는 일반적으로 다음과 같이 사용되는 setTag메서드 가 있습니다.

HorizontalLayout {
    setTag(1,"a")
    setTag(2,"b")
}

infix 로 최적화

class _Tag(val view: View) {
    infix fun <B> Int.to(that: B) =  view.setTag(this, that)
}

fun View.tag(block: _Tag.() -> Unit) {
    _Tag(this).apply(block)
}

DSL 에서 사용하기

HorizontalLayout { tag {  1 to "a"  2 to "b" } }

4.2 @DslMarker 로 범위 제한

Kotlin은 메서드의 범위를 제한할 수 있는 DSL 사용 시나리오에 대한 @DslMarker주석 합니다. 주석이 달린 람다 this에서 외부 Receiver의 메서드를 호출하면 오류가 보고됩니다.
즉,객체 중 가장 마지막이 this되는 객체 이외는 this으로서 사용할 수 없습니다.

HorizontalLayout {// this: LinearLayout
    ...
    TextView {//this : TextView
        // 此处仍然可以调用 HorizontalLayout
        HorizontalLayout {
            ...
        }
    }

}

DslMarker를 재정의 해 줍시다.

@DslMarker  
@Target(AnnotationTarget.TYPE)  
annotation  class ViewDslMarker
fun ViewGroup.TextView(init: (@ViewDslMarker TextView).() -> Unit) {
    addView(TextView(context).apply(init))
}

4.3 클래스 및 인터페이스 정의

먼저 여러 메서드 인터페이스를 포함하는 클래스와 이 인터페이스를 사용해야 하는 클래스를 정의합니다. Kotlin에서 DSL을 사용하는 방법을 단계별로 보여줍니다.

interface Listener {
    fun onStart()

    fun onNext()

    fun onComplete(result:String)
}

class Work {
    private var listener: Listener? = null
    fun start() {
        listener?.run {
            onStart()
            onNext()
            onComplete("onComplete")
        }
    }

    fun addListener(listener: Listener) {
        this.listener = listener
    }
    //doSomething
}
}

보통의 사용방법

    val  work = Work()
    work.addListener(object :Listener{
        override fun onStart() {

        }

        override fun onNext() {

        }

        override fun onComplete(result:String) {

        }
    })

DSL로 바꾸어보자
일단 인터페이스가 가지는 함수들을 만들어 고차함수 를 받을 수있도록 한다.

//别名
typealias onEmpty = () -> Unit
typealias onResult = (String) -> Unit

class ListenerDSL {
    var _onStart: onEmpty? = null
    var _onNext: onEmpty? = null
    var _onComplete: onResult? = null


    fun onStart(onEmpty: onEmpty) {
        _onStart = onEmpty
    }

    fun onNext(onEmpty: onEmpty) {
        _onNext = onEmpty
    }

    fun onComplete(onNext: onResult) {
        _onComplete = onNext
    }
}

그런 다음 확장 기능의 쓰기 DSL을 사용합니다.

fun Work.addListenerDSL(init: ListenerDSL.() -> Unit) {
    val listenerDSL = ListenerDSL()
    listenerDSL.init()
    this.addListener(object : Listener {
        override fun onStart() {
            listenerDSL._onStart?.invoke()
        }

        override fun onNext() {
            listenerDSL._onNext?.invoke()
        }

        override fun onComplete(result: String) {
            listenerDSL._onComplete?.invoke(result)
        }
    })
}

사용 할때는 아래처럼 깔끔합니다.

   val work = Work()
    work.addListenerDSL {
        onStart { println("onStart") }
        onComplete { result -> println(result) }
    }
    work.start()

참고자료

Mockk

Mockk

MockK ( with Kotlin )

mockk.io 공식사이트
최근 kotlin+ TDD 위주로 개발하는 경험을 하게되어 어떤 라이브러리를 쓰나 했더니 MockK를 쓰고있었다. Mokito처럼 어노테이션을 이용하여 목업을 할수 있어서 Mokito사용하라면 금방익숙해질수 있다.
어노테이션이나 함수들도 비교적 직관적으로 되어 있어서 테스트코드를 읽다보면 대충 어떤 상황인지 쉽게 파악알수 있다.

자주사용하는 키워드

  • mockk<객체타입이>() : 지정된 타입의 클래스를 목업으로 하는 목업객체를 만든다. (@MockK 어노테이션으로 해도된다)
  • every{} : {}안에 지정된 블럭이 테스트중에 실행된다면 아래의 return값을 돌려준다.
  • returns : 지정된 객체(값)를 every 블럭이 실행될때마다 돌려준다.
  • answers{} : returns가 한개의 객체만 리턴한다면, answers는 실행블럭(여러실행함수들이 담긴)을 리턴할수 있다.
  • verify{} : mockk로 지정한 객체의 특정함수, 속성등이 호출되었는지 검증한다. verifySequence,verifyOrder 를 사용하면 함수가 지정된 순서대로 호출되었는지도 검증할수있다.
  • justRun{} : 단지 어떤 함수를 목업형태로 실행하고 싶을때 사용한다.(kotlin Unit값을 리턴한다.)
  • spyk<객체타입> () : 객체를 Reflection한 목업객체를 만든다. 이때 원본 객체의 프로퍼티등도 직접 값을 바꿀수 있다.
  • any() : every내에서 사용하는 목업함수의 인자를 any, 즉 아무값이나 받았다고 가정한다. 즉 어떤 함수에 인자값이 뭐가 오든간에 라는 의미이다. allAny()도 있는데 인자의 개수에 상관없이 모두 any로 취급해준다.
  • relaxed = true : 목업객체나 리턴값 또는 함수인자에 이것을 지정하면, 목업객체(또는 함수)의 모든 응답은 객체 (또는 함수)자신의 리턴값을 리턴할수 있도록 여유(relax)를 준다. 따라서 목업된 객체의 함수들이 실행될때 answer 가 지정안되었다는 에러를 회피할수있다.
  • slot-capture : 슬럿은 말그대로 무언가 담을 그릇이고, 캡춰는 슬롯에 값이 담길때 그값을 잡아주는 역활을 한다. every 에서 함수를 실행시 특정 인자값을 함수에 전달(이때 캡춰한다)해서 그 값을 다시 return(answer) 에서 사용하고자 할때 slot-capture를 사용한다. 슬럿은 함수의 인자형태에 맞게 미리 변수로 선언되어있어야한다.

코틀린의 코루틴을 위한 함수도 제공한다.
coEvery, coVerify, coMatch, coAssert, coRun, coAnswers or coInvoke

@Test
fun 공홈샘플테스트코드() {
    // arrange
	val car = mockk<Car>()  // Car를 목업객체로 한다.
	val slot = slot<String>() // 슬럿을 만들어 둔다.
	every { car.drive(Direction.NORTH) } returns Outcome.OK // 목업된 객체의 drive함수가 실행되면 Outcome.OK 가 리턴된다. 물론 drive함수의 리턴값과 같은 형식이어야한다. 
	every { car.fly(any()) } returns mockk(relexed = true) // fly 함수는 어떤 리턴값이라도 검증에는 상관없다.
	every { car.rider(person = capture(slot) } returns "${slot.captured}가 타고있어" // slot-capture 함수를 이용해서 every의 rider를 실행할때 넘겨준 인자값을 결과값에 활용할 수있다.
	
	// act
	val result = car.drive(Direction.NORTH) // 목업객체의 drive를 실행했다.
	car.fly("비행기냐?") 
	val result2 = car.rider(person = "태발")
	
	// assert
	verify { car.drive(Direction.NORTH) } // car.drive(Direction.NORTH) 형태로 출력이 되었는가를 검사한다. 호출이 안되었더라면 failed가 된다.
	verify { car.fly(any()) }
	verify { car.rider(person = "태발") }
	
	confirmVerified(car)  // 목업객체의 every로 지정된 함수들이 모두 불리고 verify되었는지 재차 확인한다. 하나라도 verify 되지 않는 항목이 있다면 failed가 된다.
	assertEquals(result, Outcome.OK) // 예상값이 일치하는지 확인한다.
	assertEquals(result2, "태발가 타고있어") // 예상값이 일치하는지 확인한다.
}

라이브러리 추가

라이브러리추가는 Gradle에
testImplementation “io.mockk:mockk:{version}”
(안드로이드라면 이것도 추가)androidTestImplementation “io.mockk:mockk-android:{version}”
하면 테스트코드에 mockk를 사용할수 있다.

Kotlin Multiplatform

Kotlin Multiplatform

KMP , KMM

Kotlin Multi Platform (Mobile) 의 줄임말이다.
Kotlin으로 Android,iOS, Jvm(javaFx or Spring App), Javascript, Desktop(with jet composer) 프로젝트 만들수 있다.
Kotlin으로 저걸 다 만들자가 아니라, Kotlin으로 도메인의 비지니스로직을 만들고 각각의 플랫폼에서 공통으로 사용할수 있는 환경을 만들어 주자는 거다.
각 플랫폼은 화면단의 처리만 한다고 생각하면 된다. 각 플랫폼만의 특징이 있기떄문에 그걸 하나의 코드로 퉁치는것은 힘들고 또한 각각의 네이티브한 표시기법이 있기때문에 그것은 그대로 살려두고, 그 안쪽의 것만 같은 것을 사용할수 있도록 해준다는 개념이다.
따라서 플러터나 리액티브 처럼 하나의 코드로 여기저기 실행됩니다 라는 개념은 아니라 common모듈(이름은 개발자맘대로해도된다.)을 각 플랫폼에서 동일한 방식으로 호출하는것에 개발초점을 두면된다.

프로젝트 시작/설정

IntelliJ IDEA 에서 새프로젝트를 Kotlin-> Multiplatform 항목에서 선택하여 생성하면된다. Mobile Application을 선택하면 된다.
일단 생성하고 나중에 JVM, Javascript등을 수동으로 추가하면 된다.

1. setttings.gradle.kts

프로젝트를 생성하고 Project 탭의 화면에 보면 settings.gradle.kts 파일이 있다.
이 화일에서는 빌드 전체에 사용하는 (그래이들용)플러그인들에 영향을 주는 설정을 할수 있다.

  • 각 서브 프로젝트나 모듈들은 플러그인 항목을 통해서 사용할 플러그인을 지정하는데 이때 플러그인을 찾을 저장소와 버젼등을 지정할수 있도록 해준다.
    setting.gradle.kts
pluginManagement {  
  repositories {  
    여기선 레포지트리 gogole(), mavenCenter() 등을 지정한다.
    별도의 저장소가 있다면 해당 저장소를 등록하면된다. 
  }  

  resolutionStrategy {  
    여러개의 플러그인이 서로 다른 버젼으로 모듈마다 지정되어있을때 이곳에서 하나의 버젼으로 
    eachPlugin {  
      여기선 읽어들인 플러그인에 대해서 사용할 모듈이나 버젼들을 지정할수 있다. 
      기본적으로 프로젝트에서 id("플러그인ID")를 입력하면 저장소에서 해당 id의 플러그인을 최선버젼으로 가져온다.
      예를 들어 kotlin2js라는 플러그인을 호출할때 "org.jetbrains.kotlin:kotlin-gradle-plugin" 모듈을 호출하고 싶을때는
      if (requested.id.id == "kotlin2js") {  
          useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")  
          useVersion("1")
      }       
     라고 쓰면된다. 
     버젼도 지정할수 있다.

        }
     }  
  }  
// 하위 프로젝트나 모듈들은 build.gradle.kts 파일에서 아래처럼 플러그인을 지정해서 사용한다.
plugins {
    id("com.mycompany.sample")
    kotlin("js")
}

예를들어 각각의 모듈에서 Serializeation 플러그인을 사용할때,

  1. js-app : kotlin(“plugin.serialization”) version “1.3.0”
  2. androd-app:kotlin(“plugin.serialization”) (verstion을 지정안하면 최신버젼을 가져온다.)

처럼 각각의 모듈들이 서로다른 버젼의 사용으로 인해 문제가 발생하거나 최종화일이 쓸데없이 커질때, 가능하면 하나의 버젼으로 통일시키고 싶을때는 각각의 모듈설정파일 gradle.build.kts 에서 하는게 아니라 setting.gralde.kts 에서 한번에 지정하면된다.
if (requested.id.id == “org.jetbrains.kotlin.plugin.serialization”) {
useVersion(“1.5.31”)
}
위 처럼 하면 빌드시에 org.jetbrains.kotlin.plugin.serialization id를 가진 플러그인 요청에 대해 무조건 1.5.31 을 사용하도록 한다.
이렇게 resolutionStragy(해결전략) 을 짤수 있도록 해준다.

이방식으로는 classpath 라는것으로 해당 플러그인의 이름과 버젼을 classpath(“그룹:이름:버젼”) 식으로 지정했는데, resolutionStragy 를 이용하면 classpath를 사용하지 않고 useModule을 통해서 지정할수 있다.

if (requested.id.id == “com.android.application”) {
useModule(“com.android.tools.build:gradle:4.2.2”)
또는
useVersion(“4.2.2”)
}
이렇게 지정하면 하위의 build.gradle.kts 에서는 classpath(“com.android.tools.build:gradle:4.2.2”) 를 지정할 필요가없다.

  • settings.gradle.kts는 사용할 모듈을 include로 지정한다.
    레포지토리를 따로 관리하기 위해서는 project(":androidApp").projectDir = file("…/androidApp") 처럼할수도있다.

2.build.gradle.kts

작업중인 프로젝트 전체에 영향을 주는 빌드스크립트이다.
주로 각 하위모듈에서 참고할 저장소와 의존성(사용할 라이브러리가 어디서오고 어떤 버젼을 사용하는지)을 지정할수 있다.
같은 라이브러리여도 버젼에 따라서 사용법과 기능이 차이가 있기때문에 의존관계를 지정하여 주는거다.

group : 외부참조용 프로젝트 그룹명을 지정한다. 라이브러리나 플러그인을 불러올때 ( "그룹명:이름:버젼") 으로 참조하여 가져오는데 그때 사용할 그룹명을 지정한다. 
version: 위에서 말한 버젼이다. 
allprojects {
  1. by extra 로 변수 사용
     아래처럼 by extra 를 사용하면 하위 gradle에서 [val momo: Map<String, String> by extra] 처럼 선언하여 프로젝트전역에 걸친 변수를 사용할수 있다.
  val momo by extra {
    mapOf(  
       "springBoot" to "2.2.0.M6"  
   )
  }
  2. task.create(register) 
     프로젝트의 모든 모듈에 동일한 작업을 수행하는 task를 생성해준다. 
  3. repositories
     하위 모듈들이 빌드시에 참조할 저장소를 지정한다.
  4. configurations.all 
     이곳에서는 resolutionStrategy 을 이용해서 라이브러리 모듈을 강제로 특정 모듈 또는 버젼으로 지정한다든지, 다른 모듈로 대치하는 등의 처리를 할 수있다.
     https://star-zero.medium.com/gradle%E3%81%AEresolutionstrategy-dc948fb05b54 참고
  5. group, version
     이곳에 그룹과 버젼을 입력하면 전체 프로젝트가 동일한 그룹과버젼으로 된다.
     
} 

subprojects {
allprojects와 설정은 동일하다. 최상위프로젝트와 하위 프로젝트들을 다르게 설정할경우 이 블록을 사용한다.
}

project(":api명"){
  dependencies{}  특정 프로젝트가 실행되기 전에 먼저 빌드가 이루어져야 할경우 사용한다.
}

하위프로젝트

KMM으로 프로젝트를 생성했다면 기본적으로 android-app , iosApp, Shared 라는 하위프로젝트(모듈)이 생성되어 진다.
kotlin 멀티플랫폼에서는 이렇게 하위 프로젝트로서 각각의 플랫폼에 해당하는 폴더를 생성하고, shared 라이브러리에서 작성한 공통코드를 각각의 플랫폼에서 불러다 사용할수 있도록 해주는 구조이다.
따라서 각각의 플랫폼폴더의 구조는 각 애플리케이션의 작업폴더구조대로 작업하면된다.
플랫폼을 추가할수도 있는데, 루트프로젝트폴더에 해당플랫폼의 폴더를 만들고 그안에 build.gradle.kts 파일을 만들면된다.
만들고 나서는 프로젝트의 루트 settings.gradle.kts에 include(":프로젝트명") 을 입력하여 모듈을 추가해야한다.
그리고 shared/src의 하위 폴더를 생성하여 공통처리될 코드를 입력하면 된다.

Shared폴더에 구현하는 코드는 크게 3가지 형태로 나뉜다.

  1. 플래폼에 의존하지 않는 데이터 처리. kotlin 멀티플랫폼에서 기본적으로 지원하는 기능들을 이용해서 구현할 경우 프로젝트에서 shared라이브러리를 참조 하는 것만으로도 불러와 사용할수 있다.
  2. 플랫폼에 의존하는 고유한 처리. 각각의 플래폼에서 데이터를 얻을수 밖에 없는 고유한 처리의 경우 예를 들어 모바일의 경우는 단말의 정보등은 shared 모듈에서 알수가 없는 정보이기에, 에상(expect) 와 실체(actual) 키워드로 지정하여 작업한다. shared에서 예상한 기능이 해당 플래폼에서의 실체는 이렇게 동작한다는 형태로 구현하여 각 플랫폼에 적절한 처리가 되도록 한다.
  3. 플랫폼에 의존하지만 공통으로 사용할 수 있도록 라이브러리를 사용한처리. kotlin 멀티플랫폼용 라이브러리가 있어서 여러플랫폼에서 동일하게 구현될수 있도록 해준다. 이때는 expect와 actual 없이 작업해도 된다. 예를 들어 시간,네트워크 같은 라이브러리가 있다.

서로다른 플랫폼에서 공통의 작업, 예를 들어 Android , iOS 각각의 플랫폼에서 네트워크로 부터 데이터를 요청하는 작업을 할때 각 플랫폼에서 동작할 예상(expect) 라는 키워드로

buildSrc

각 프로젝트마다 build.gradle.kts파일에 사용할라이브러리와 플러그인의 이름과 버젼을 직접입력하여 관리하는것은 매우 불편하다.
특히 KMM(or KMP)에서는 각기 다른 플랫폼별로 build.gradle.kts파일이 있어서 공통된 특정 라이브러리의 버젼이 바뀔때 마다 해당 하는것은 실수를 부르기 마련이다.
루트 프로젝트의 바로 하위로 buildSrc라는 폴더를 생성하고 모듈로 인식할수 있도록 build.gralde.kts 파일을 두고 , src/main/java/ 폴더를 만들어서 하위에 .kt 파일로 object 형태로 변수와 패키지명을 정의하여 다른 build.gradle.kts에서 해당 오브젝트 변수를 가져와서 설정할수 있다.

  1. KMM으로 멀티 플랫폼을 만들자(Android,iOS용)
  2. 프로젝트 루트폴더밑에 buildSrc 를 만들고 그 안에 build.gradle.kts를 만든다
repositories {  
  jcenter() 
  google()  
}  
  
plugins {  
  `kotlin-dsl`  
}
  1. src/main/java/Versions.kt 파일을 만들고, 버전정보를 입력한다.
Version.kt
object Versions {  
    // Kotlin  
  const val kotlin = "1.5.31"  
  
  // Android  
  const val androidMinSdk = 21  
  const val androidCompileSdk = 31  
  const val androidTargetSdk = androidCompileSdk
  
  // AndroidX 
  const val androidx = "1.0.0"
  const val androidNavigation = "2.3.0"
}

프로젝트에 따라서 AndroidDeps, SharedDeps, SpringBootDeps 등등을 추가하면된다.

AndroidDeps.kt 
object AndroidDeps {
	object Plugins {  
	    object Kotlin {  
	        val android = "android"  
			val kapt = "kapt"  
	  }  
	  
	    object Id {  
	        val androidApp = "com.android.application"  
		    val safeArgs = "androidx.navigation.safeargs.kotlin"  
	  }  
	}    
  
	object AndroidVersions {  
	  val minSdk = 24  
	  val targetSdk = 30  
	  val compileSdk = 30  
	}
	  
	object ClassPath {  
	    val safeArgsGradlePlugin =  
	        "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.androidNavigation}"  
	}
}
  1. 프로젝트의 build.gradle.kt 파일을 수정하자.
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31")를 
classpath(Deps.ClassPath.kotlinGradlePlugin)로 고치자
    
classpath("com.android.tools.build:gradle:4.1.2")를 
classpath(Versions.ClassPaths.androidGradle)로 고치자
  1. androidApp 프로젝트의 build.gradle.kt파일을 수정하자
kotlin("android")를 
kotlin(AndroidDeps.Plugins.Kotlin.android)로 고치자
    
minSdkVersion(24)를 
classpath(AndroidDeps.AndroidVersions.minSdk)로 고치자

이렇게 buildSrc폴더하위에 라이브러리, 플러그인들의 버젼과 이름을 명시하여 같은 라이브러리를 사용하는 여러 모듈에서 일관성있게 관리할수 있다.

shared 폴더

shared 폴더는 모든 플랫폼에서 공통으로 처리될 만한 (주로) 비지니스 로직(도메인)이 들어간다.
shared/build.gradle.kts 파일에는 각 플랫폼별 설정과 플랫폼별 사용하게될 소스코드의 위치를 지정한다.
각 플랫폼별 코드를 작성하기 위해서는 *Main 이라는 이름으로 폴더를 생성하고 sourceSets {} 에 폴더의 이름을 지정하면된다. KMM 프로젝트를 생성했다면 기본적으로 androidMain, iosMain 그리고 commonMain 폴더가 생성되어있고 sourceSet 에는 각각 지정되어 있다.

 
kotlin {  
     android()  
     ios {  
   	  binaries {  
   		  framework {  
   			  baseName = "shared"  
   		  }  
   	 } 
    }  
    sourceSets {  
     val commonMain by getting  
     val commonTest by getting {  
   	  dependencies {  
   			  implementation(kotlin("test"))  
          }  
      }  
      val androidMain by getting {  
         dependencies {  
   		      implementation("com.google.android.material:material:1.2.1")  
        }  
      }  
      val androidTest by getting {  
         dependencies {  
           implementation("junit:junit:4.13")  
          }  
        }  
      val iosMain by getting  
      val iosTest by getting  
   }  
}

shared/gradle.build.kts 파일은 안드로이드 라이브러리 형태로 안드로이드앱에 포함되기 때문에, plugin 에 com.android.library 플러그인이 포함되어 있고, android{} 블럭에서 안드로이드 라이브러리로서의 설정이 있다.

iOS앱의 경우는 .framework 파일형태로 패키징되어, iosApp 프로젝트 폴더의 iosApp.xcodeproj 파일을 Xcode에서 열어서 iosAPP을 만들수 있다.

이밖에 Javascript, SpringBoot, Desktop(jvm 기반)의 앱 등을 추가할수 있다.

플랫폼 추가

예를 들어 spring-boot 프로그램을 포함하고 싶다면, 루트프로젝터하위에 pring-boot-app(이름은 상관없다)을 만들어 build.gradle.kts를 생성하고
스프링부트 실행에 필요한 의존파일들 과 kotlin 플러그인을 추가하고 src/main/java/com.abcde 폴더에 작업소스를 넣으면된다.
이때 settings.gradle.kts 파일에 include(":spring-boot-app") 이라고 아까 만든 이름을 추가하여 준다.
최종으로 shared/build.gradle.kts 파일에

kotlin {
 ...
    jvm()
 ...
     sourceSets {
        val jvmMain by getting {
            dependencies {
                implementation (kotlin("stdlib"))
                implementation("junit:junit:4.13.1")
            }
        }
     }
}

를 추가하고 shared/jvmMain 폴더도 만들어서 spring-boot 애플리케이션에서 처리할 부분을 추가하자.(밑에는 예이다)

package me.kimtaeho.shared  
  
actual class Platform actual constructor() {  
    actual val platform: String = System.getProperty("os.name")  
}
//주로 맥에서 개발하기 떄문에 Hello, Mac OS X! 가 출력될것이다.

commonMain폴더의 Platform.kt 파일에 정의된 expect Platform 클랙스와 expect platform 변수를 jvmMain 폴더의 Platform.kt파일에서 actual로 구현한것을 볼수 있다.
iosMain 폴더를 보면은 kotlin 에서 iOS프레임워크의 UIKit를 kotlin에서 불러낼수 있도록 지원해주는 것을 알수 있다.

import platform.UIKit.UIDevice  
  
actual class Platform actual constructor() {  
    actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion  
}
//Xcode에서 iosApp프로젝트 파일을 열어서 실행해보면 실행된 단말/에뮬레이터의 버젼이 출력된다.

Multiplatform 에 사용할수 있는 라이브러리

KMP, KMM 에서 공통처리를 위한 로직을 만들때 해당기능이 플랫폼에서 사용할수 있는지를 알아야한다.
네트워크같은 경우 네트워크라이브러리를 통해서 멀티플랫폼에 대응하여야는데 Ktor라는 제품을 이용한다.
그밖의 라이브러리 들은
https://github.com/AAkira/Kotlin-Multiplatform-Libraries
여기를 참고하면된다.

Shared 에서 무엇을 공통처리 할것인가

KMP, KMM 라는 솔류션으로 무엇을 어떻게 공통처리 할것인가가 처음에는 크게 와닿지 않을지도 모른다.
공식예제에서는 플랫폼의 OS 이름과 버젼을 출력하는것이 있는데 그런거하려고 멀티플랫폼 이라는 거창한 서비스를 이용하진 않는다.
우리가 앱을 포함하는 하나의 서비스를 만들때, 서버따로, 웹따로 모바일 따로따로 각각의 처리로직을 각각의 팀에서 개발해왔다.
예를 들어, API서버에 요청을 하는 처리가 있다고 할때, Android와 iOS각각의 소스코드에서 각각 필요한 라이브러리를 사용하여 API로부터 데이터를 호출하고 처리한다. 이때 Android, iOS에서는 API에서 가져오는 데이터(JSON ,XML등)을 각각 처리하도록 하는데 이과정에서 개발자들이 서로 다른 처리를 하는경우가 종종생긴다.
KMP 에서는 이러한 공통된 처리를 shared라는 한군데서 처리할수있도록 하여 복잡한 중복로직을 개발하는 공수를 덜게 해준다.
물론 각 플랫폼의 화면처리에 관한것은 플랫폼 고유의 처리에 맡기고 있다.
이렇게 Shared 는 프로그램의 좀더 깊숙한 부분에서 처리되는 비지니스 로직을 담당하도록 하여 개발시간, 프로그램 테스트 시간을 단축시켜준다.

그럼 다시, Shared 에서 무엇을 공통처리 할 것인가 라고 묻는다면
네트워크처리, 도메인로직 처리, 레포지토리 처리, 이벤트로그(분석용) 처리 등을 플랫폼에서 공통으로 사용할 로직들이라고 할수 있다.

2022년 8월 28일 일요일

데일리/ 티켓 작성시에

 

데일리 작업 작성시에

  1. [ ] 어제 작업한 것을 작업단위(티켓)별 서술

    1. [ ] 담당 티켓 번호

    2. [ ] 진척상황, 작업 진행 상황 설명(5W1H)

    3. [ ] 지연의 이유를 기술(5W1H)

  2. [ ] 오늘 작업할 것을 작업단위(티켓)별 서술

    1. [ ] 담당 티켓번호

    2. [ ] 현재 까지의 진척상황

    3. [ ] 오늘 진행할 작업 상황

    4. [ ] 남은 작업 예상 소요 시간

    5. [ ] 티켓의 종합테스트 및 릴리스 예정일 파악

    6. [ ] 지연예상점 문제발생예상되는 부분을 미리 고지

티켓 작성시에

  1. [ ] 티켓 제목 작성 규칙 (조사 / 개발 / 버그수정)

  2. [ ] 티켓의 담당자, 에픽, 스토리포인트, 작업일정 입력

    1. [ ] 티켓의 에픽

    2. [ ] 스토리포인트

    3. [ ] 시작일-종료일

    4. [ ] 담당자

  3. [ ] 티켓의 내용별 템플릿 참조하여 작성하기

    1. [ ] 티켓내용에 따른  템플릿

    2. [ ] 티켓내용에 대한 회의내용 링크

    3. [ ] 관련 이슈 티켓의 링크

200X.01.01일지


시간

대화내용

대화상대

비고









2021년 11월 16일 화요일

PlantUML 편집을 쉽게하기

PlantUML 편집을 쉽게하기

Android Studio 나 IntelliJ를 설치하여 plantUML plugin 을 설치하면 편집이 세상쉽다.

  1. 구글에서 android studio 를 검색해서 사이트로 가서 안드로이드 스튜디오를 설치한다.

enter image description hereenter image description here

  1. android studio 프로그램을 아무프로젝트나 만들든지 해서 시작하고, Preferences -> Plugins ->MarketPlace탭 에서 plant를 입력하면 [PlantUML integration] 이 나오는데 이를 인스톨하고 재시작한다.enter image description here
  2. 프로그램을 재시작한후에 File->new에 가보면 Plaunt UML 항목이 있다.enter image description here
  3. 생성된 파일을 열어서 이것저것 입력해보면 오른쪽에 미리보기가 나타나고 , 각 항목을 클릭하면 해당 라인에 선택이 된다. @,.@enter image description here
  4. 이런것도 만들수 있고…enter image description here
  5. 오른쪽 버튼을 누르면 저장하기도 된다.enter image description here
  6. 당연히 Git에 올리면 버젼관리도 된다.