Team DIVE introduction

J124 J153 J190 S002 S015
유선규(sunkest) 이유택(lcpnine) 조병건(marulloc) 강병민(mike123789-dev) 류연수(yeonduing)

Link to Web Dev Logs
Link to iOS Dev Logs





Project introduction

다양한 프로젝트에서 독립적으로 적용 가능한 User Event Collector 제작

User Event ? UI/UX 개선 등을 위해 수집하는 Event


우리의 이벤트 수집기 "DIVE"

프로젝트 마다 **의미 있는 이벤트**는 다를 것 입니다. **DIVE**는 자유도 높게 설계되어, **의미 있는 이벤트**의 범주를 개발자가 정할 수 있습니다.

즉, 개발자가 원한다면, 기본적으로 제공되는 이벤트 뿐만 아니라 웹의 경우, 단일 이벤트 여러개로 이루어진 복합 이벤트를 수집하거나, 앱의 경우, 앱을 종료하거나 노래를 재생하는 이벤트들을 수집할 수 있습니다.

**DIVE**를 이용하여, 원하는 이벤트를 수집할 수 있으며, 수집한 데이터는 UI/UX 개선에 중요한 데이터가 될 것입니다.


DIVE의 동작을 보이기 위하여, Naver 스트리밍 서비스 VIBEClone하여 이벤트를 수집했습니다. Clone scope에서 VIBE의 추천 기능과 음원 재생 기능은 제외

Link to Web DIVE ReadMe

Link to iOS DIVE ReadMe





Technology Stack

스크린샷(163)





DIVE ReadME

Web Event Collector

React 프로젝트라면, 어디서든 사용이 가능한 Event 수집기입니다.

**Config Object**를 작성하고, 이벤트 로깅을 원하는 컴포넌트를 **Emitter**로 감싸고, 부모 컴포넌트 어딘가에서 **Collector**로 감싸준다면 끝입니다.

간단한 Native Event에 사용자가 원하는 의미를 부여할 수 있으며, 여러 event로 구성된 복합 이벤트에도 의미를 부여하여 로깅할 수 있습니다.


구성요소

1. Event Config Object   사용자가 원하는 Event들이 나열되어 있다.   조건이 충족되면, 사용자에게 주어질 Event들을 정의   Event의 옵션들을 정의   여러개의 Config Object가 존재 할 수 있다.   원하는 Collector에게 원하는 Config Object를 넘길 수 있다.

2. Collector   config object에서 정의된 이벤트들을 해석   Emiiter로부터 전파되는 이벤트들을 감지 및 필터링   감지된 이벤트에 대해 dispatch로 전달   dispatch: 사용자가 직접 정의할 수 있는 이벤트 핸들러.

3. Emitter   사용자가 정의한 Event가 발생하는 Wrapping Component   Event가 Bubbling되기 전, Event 객체에 props와 Event ID를 삽입한다.   Native Event가 아니라, 컴포넌트의 상태 값을 조건으로 Event를 발생시킬 수 있다.

4. Dispatch   Collector의 인자로 넘겨야 하는 함수로 사용자가 직접 정의해야 합니다.   로깅되는 이벤트의 getter함수로 생각하면 됩니다.   Collector는 사용자가 정의한 이벤트 발생 조건을 만족하면 dispatch를 호출합니다.   따라서, dispatch 함수는, 이벤트가 발생했을 때 처리하는 로직을 담고 있어야 합니다.


Event Config Object

Simple events

const example1: EventObject = {
  simple: {
    identifier_1: {
      event_id: 1,
      event_type: "click",
      once: true,
      description: "너무 잼따",
    },
    identifier_2: {
      event_id: 2,
      event_type: "mouseover",
      once: true,
      description: "이건 두번째 식별자에요~",
    },
  }
}
  • **Simple**에는 단일 Event를 정의합니다. line [2]
  • DIVE(our event collector)는 "identifier_1"과 같이 string으로 event를 식별합니다. 따라서 identifier_1과 같이 객체의 key값은 Unique해야 됩니다. line [3][9]
  • **DIVE**는 사용자가 event_type에 입력한 event에 따라, event log를 내보냅니다. **DIVE**는 Native Event를 대부분 지원합니다.line [5][11]
  • 또한 ["click","mouseover"]와 같이 입력을 한다면, **Emitter**가 감싸는 컴포넌트에서 click, mouseover에 대해 DIVE가 Event log를 남깁니다.
  • **description**과 **once**는 optional입니다. description을 입력한다면, event log에 description이 담길 것입니다. once:true를 입력한다면, event log를 한번만 남기게 됩니다. default값은 false 입니다.

Complex events

import { EventObject } from "./collector";

const example1: EventObject = {
  simple: {
    identifier_1: {
      event_id: 1,
      event_type: "click",
      once: true,
      description: "너무 잼따",
    },
    identifier_2: {
      event_id: 2,
      event_type: "mouseover",
      once: true,
      description: "이건 두번째 식별자에요~",
    },
    identifier_3: {
      event_id: 4,
      event_type: "click",
      once: false,
      description: "이게 바로 스트레스 테스트",
    },
  },
  complex: {
    {
      timer: 3000,
      sequence: ["identifier_1", "identifier_2", "identifier_1"],
      event_id: 3,
      event_type: "compext test1",
      once: false,
      description: "complex event 테스트 임둥",
    },
  },
};
export default example1;

  • **complex**에는 복합 이벤트를 정의합니다.
  • **sequence**에는, simple에서 정의한 단일 이벤트의 식별자가 배열 형태로 들어갑니다.
  • sequence에 나열한 이벤트들이, **timer**에 작성한 ms가 끝날 때 까지 모두 발생한다면, 이벤트를 로깅합니다. 여기선, 3초 안에, identifier1, identifier2, identifier1이 발생할 때, 이벤트를 로깅합니다.

Note: 이벤트의 로깅은 Collector에서 이뤄집니다. 이벤트 로깅이 발생하면, Collector는 사용자가 넘긴 dispatch함수를 호출합니다. 이벤트 객체를 dispatch 함수의 인자로 넘기면서, 사용자가 dispatch를 정의한대로 event를 사용할 수 있습니다.



Collector & Emitter

Example Code

const IndexPage = () => {
  return (
    <Collector eventConfig={example1} dispatch={(e) => {console.log(e)}}>
      <div>
        <Emitter identifier="testH1Event" eventType={["click"]}>
          <h1>Click Event Tester</h1>
        </Emitter>
        <div>Nothing Div</div>
        <Emitter identifier="testComponentEvent" eventType={["mouseover"]}>
          <TestComponent />
        </Emitter>
      </div>
    </Collector>
  );
};

Collector

  • **Collector**는 하나의 React Component로, 위 코드에서와 같이 원하는 컴포넌트 바깥쪽에 래핑합니다.
  • 하나의 Collector 안쪽에 있는 여러 개의 **Emitte**r들로 부터 발생된 모든 이벤트는 모두 같은 Collector에서 수집됩니다.
  • 하나의 Complex Event에 포함되는 Simple Event 들은 하나의 Collector안에 작성되어야 합니다.
  • 주어지는 props는 다음과 같습니다.
    • eventConfig: 앞서 설명된 이벤트 정의 object
    • dispatch: 해당 Collector에서 이벤트 감지시 호출될 콜백 메서드. dispatch의 작성에 대해서는 후술됩니다.

      Note: Collector를 어떤 컴포넌트에 래핑할 것인지 정하는 것은 개발자의 몫입니다. 다만, 전역으로 사용하거나, 너무 큰 페이지와 같은 컴포넌트에 사용된다면 하나의 Collector에 버블링되는 이벤트가 과도하게 많아질 수 있습니다. 가능하다면, 적절히 Collector를 분리하는 것을 권장합니다.


Emitter

  • Emitter 또한 하나의 React Component로, 기본 이벤트 수집 대상 컴포넌트 바로 바깥쪽에 래핑합니다.
  • 주어지는 props는 다음과 같습니다.
    • identifier: 이벤트가 발생할 컴포넌트를 구분할 식별자로, 문자열 형태입니다. config object에 정의한 식별자가 들어갑니다.
    • eventType: 이벤트의 종류로, 브라우저에서 지원되는 이벤트들이 들어가며, 그 종류는 아래와 같습니다. 문자열의 배열 형태로 입력받으며 여러 개를 입력할 수도 있습니다.
  • Emitter로 감싼 컴포넌트에서 eventType으로 지정한 이벤트가 발생된 경우, identifier, 브라우저의 nativeEvent정보, props 등 몇가지 정보를 담은 객체를 부모 요소로 버블링합니다.
  • 이렇에 버블링 되는 이벤트는 Collector를 만날 때 까지 버블링되며, Collector를 만나면 config object 정보와, identifier, eventType을 비교하여 필터링되고, 수집 대상인 이벤트의 경우 dispatch에 전달됩니다.

Note: 컴포넌트의 상태 값을 event 발생 조건으로 삼기 위해, 금주 내로, Emitter에 boolean 형을 반환하는 함수를 받을 것입니다. boolean 형을 반환하는 함수는, 원하는 상태 값이 임계점(사용자가 지정한)을 넘어가면 true를 반환해야 합니다. Emitter는 이벤트가 발생할 때마다, 함수를 호출하고, 반환값이 true가 아니라면, stopPropagation()을 통해, 이벤트가 버블링되는 것을 막을 것입니다. 이벤트 버블링이 되지 않으므로, Collector에선 이벤트를 수집 할 수 없습니다.


Dispatch

  • **Collector**의 인자로 넘겨지는 함수로, 사용자가 직접 정의해야 합니다.
  • dispatch={console.log}와 같이 넘겨집니다. 정의된 이벤트가 발생하면, **Collector**는 바로 **dispatch**를 호출합니다.
  • dispatch에 넘겨지는 인자는 다음과 같습니다.
    dispatch({
      userEvent : {user가 config object에 정의한 객체},
      data : {Emitter가 래핑하고 있는 컴포넌트의 props},
      nativeEvent : {브라우저의 native 이벤트 객체}
    })
    
  • **data**를 통해, map을 통해 생성된 컴포넌트라도, 어느 요소를 클릭했는지 사용자가 dispatch 함수를 통해 판단할 수 있습니다.
  • **nativeEvent**와 userEvent 객체를 받아 사용자가 자유롭게 이용할 수 있게 했습니다.


iOS Event Collector


구성요소

  1. Event
    • Event Collector에서 제공하는 모든 이벤트.
    • Event 프로토콜 채택
  2. EventManager
    • 이벤트를 로깅하기 위한 최상단 API, 실제로 로깅을하지는 않고,
    • EventEngine을 이용하여 보냄.
  3. EventEngine
    • 직접적으로 로깅 전송/저장을 담당.
    • EventSendable, EventFetchable 프로토콜 채택

Event

public protocol Event: Codable {
    var name: String { get }
    var createdAt: String? { get }
    var metadata: [String: String]? { get }
}

기본적으로 제공되는 이벤트

class BaseEvent: Event {
    var name: String
    var createdAt: String?
    var metadata: [String: String]?

    public init(name: String, createdAt: String?, metadata: [String: String]?) {
        self.name = name
        self.createdAt = createdAt
        self.metadata = metadata
    }
}

Event 프로토콜을 채택하는 Custom Event의 예시

struct ScreenEvent: Event {
    var name: String
    var metadata: [String: String]?

    private init(name: String, metadata: [String: String]? = nil) {
        self.name = name
        self.metadata = metadata
    }

    static let playerPushed = ScreenEvent(name: "playerPushed")

    static let playerPopped = ScreenEvent(name: "playerPopped")

}

Event Engine

public protocol EventSendable: class {
    func send<T: Event>(_ event: T)
}

public protocol EventFetchable: class {
    func fetch() -> [BaseEvent]
}
public protocol EventSendableAndFetchable: EventSendable, EventFetchable {
}

기본적으로 제공되는 Engine

public final class MockServerEngine: EventSendable {
    public init() {

    }
    public func send<T: Event>(_ event: T) {
        print("MockServer - \(event.name)")
        event.metadata?.forEach { key, value in
            print("\(key) : \(value)")
        }
    }
}

이벤트의 흐름

이벤트의 간편한 확장성

Event protocol로 구현함으로써, 새로운 이벤트를 추가하는것은 매우 간편해집니다.

New Model Version

이벤트 type checking

Event protocol을 채택하는 custom type의 이벤트를 구현함으로써, 원하는 이벤트에 대한 자동완성 결과를 볼수 있습니다.

엔진의 다양한 구현

EventSendable, EventFetchableprotocol을 채택하는 엔진을 다양하게 구현할수 있습니다. 그리고 상황에 맞게 필요한 엔진을 갈아 끼우는것도 매우 쉽습니다.

New Model Version

또한, EventManager는 다수의 엔진을 가질 수도 있습니다. 실제로 저희 앱에서는 back end server를 위한 engine과 core data를 위한 engine 두개를 구현하고 주입했습니다.

설치

어느 프로젝트에서도 사용 가능하도록 Swift package manager를 이용한 설치가 가능합니다.

  1. Xcode 메뉴에서 File > Swift Packages > Add Package Dependency를 선택후
  2. https://github.com/mike123789-dev/DiveEventCollector 를 입력합니다

Web Dev Logs

Web Dev Logs Author
Next.js 조병건
Sequelize 떠나보내기 이유택
Prisma 도입과 사용법 이유택
Vercel로 Next app 배포하기 이유택
Jenkins로 CI 구현하기 조병건
Naver OAuth2.0 연동하기 유선규
우리만의 Storybook 활용법...
Spread Operator(...)로 props를 전달하는 것은 정말 anti-pattern인가?
조병건,이유택,유선규

iOS Dev Logs

iOS Dev Logs Author
노래 track list를 만들어보자! (part1: SwiftUI를 이용하여 Cell 디자인하기) 강병민
노래 track list를 만들어보자! (part2: List로 보여주기 및 ViewModel적용하기) 강병민
SwiftUI에서 View와 Model Binding (part1: @State와 @Binding) 강병민
SwiftUI에서 View와 Model Binding (part2: @StateObject, @ObservedObject, @Published) 강병민
SwiftUI에서 View와 Model Binding (part3: @EnvironmentObject) 강병민
Combine을 이용힌 chaining 강병민
Protocol 기반의 Analytics System을 만들어보자 강병민
SwiftUI) 사용자 이벤트 수집 및 Alert로 확인 류연수
서버로 post할 때 json 형식으로 보내기 류연수
코어데이터 마이그레이션 류연수
CoreData로 오프라인에서도 이용 가능한 앱 만들기 류연수
SwiftUI) NavigationLink와 Memory Leak 류연수
URL로 비동기 이미지 생성하기 - Combine과 Network 류연수
SwiftUI) ViewBuilder 와 guard let 류연수
SwiftUI) ObservableObject와 상속 류연수
SwiftUI) MVVM과 Combine 류연수
SwiftUI) 다이나믹 리스트 류연수