전체보기

[번역] Room auto-migrations

2021. 6. 2. 10:08

Room 2.4.0-alpha01 버전부터 지원하는 오토 마이그레이션 기능을 이용해 Room DB 마이그레이션이 좀 더 간편해졌다. 지금까지는 DB 스키마가 변경될 때마다 Migration 클래스를 구현하고 변경 사항을 Room에 정확히 명시해주어야 했다. 대부분의 경우에 이는 복잡한 SQL 쿼리의 작성과 실행에 연관되어 있었다.

 

오토 마이그레이션 기능을 이용하면, 어떤 버전에서 어떤 버전으로 마이그레이션을 원하는지 명시해주기만 하면 Room이 자동으로 컬럼 추가/삭제, 테이블 추가와 같은 간단한 마이그레이션을 진행한다. 좀 더 애매하고 복잡한 경우는 사용자가 구체적인 사항을 명시해주어 마이그레이션을 진행한다. (ex. 컬럼/테이블 이름 변경)

간단한 변경사항 마이그레이션

테이블에 새로운 컬럼을 추가하는 등의 간단한 변경사항으로 인해 버전이 변경되는 경우, 아래와 같이 @Database 어노테이션의 버전을 증가시키고 auto-migrations에 마이그레이션 될 버전 정보를 입력해준다.

@Database(
    version = 2,
    entities = [ Doggos.class ],
    autoMigrations = [
        AutoMigration (from = 1, to = 2)
    ]
)
abstract class DoggosDatabase : RoomDatabase { }

 

DB 버전이 업데이트 될 때마다 AutoMigration을 추가해주면 간단하게 마이그레이션이 가능하다.

@Database(
    version = 3,
    entities = [ Doggos.class ],
    autoMigrations = [
       AutoMigration (from = 1, to = 2),
       AutoMigration (from = 2, to = 3)
    ]
)
abstract class DoggosDatabase : RoomDatabase { }

 

복잡한 변경사항 마이그레이션

테이블 또는 컬럼의 이름이 변경되는 경우 Room은 이름이 변경된건지 삭제된건지 판단하지 못한다. 이러한 경우가 발생했을 때 Room은 컴파일 에러를 발생시키며 사용자에게 AutoMigrationSpec의 구현을 요구한다. AutoMigrationSpec 클래스는 아래 어노테이션 중 하나 이상을 사용하여 구현할 수 있다.

  • @DeleteTable(tableName)
  • @RenameTable(fromTableName, toTableName)
  • @DeleteColumn(tableName, columnName)
  • @RenameColumn(tableName, fromColumnName, toColumnName)

예를 들어, Doggos 테이블을 GoodDoggos로 변경하는 경우 아래와 같이 명시할 수 있다.

@Database(
    version = 2,
    entities = [ GoodDoggos.class ],
    autoMigrations = [
        AutoMigration (
            from = 1, 
            to = 2,
            spec = DoggosDatabase.DoggosAutoMigration::class
        )
    ]
)
abstract class DoggosDatabase : RoomDatabase {   
    @RenameTable(fromTableName = "Doggos", toTableName = "GoodDoggos")
    class DoggosAutoMigration: AutoMigrationSpec {   }
}

 

Migrations vs. Auto-migrations

수동적으로 DB를 마이그레이션 하는 경우, DB 스키마의 변경이 있을 때마다 Room에서 제공하는 Migration 클래스를 이용했었다. 한 테이블을 2개의 다른 테이블로 나눈다고 했을 때, 아래와 같이 테이블을 어떻게 나눌지, 어떤 데이터가 옮겨질지를 정의한 뒤 addMigrations() 함수를 통해 databaseBuilder()에 추가해야 한다.

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        ...
    }
}

Room.databaseBuilder(applicationContext, DoggosDatabase::class.java, "doggos-database")
    .addMigrations(MIGRATION_1_2,)
    .build()

 

Migrations와 Auto-migrations의 조합

Room은 Migrations와 Auto-migrations의 혼용을 허용한다. 예를 들어, 버전 1에서 2는 Migration을 이용하고 버전 2에서 3은 Auto-migration을 이용할 수 있다. 만약 동일한 버전에 대해 Migration과 Auto-migration이 정의되어 있으면 Migration이 수행된다.

 

내부적으로 Auto-migration은 Migration 클래스를 구성하고 있으므로, 동일한 마이그레이션 로직이 적용된다. DB에 최초 접근 시, Room은 현재 DB 버전과 @Database에 명시된 버전이 동일한지 확인한다. 버전이 다른 경우 해당 버전으로의 마이그레이션 경로를 찾아 연속적으로 마이그레이션이 수행된다.

테스트

Migrations 또는 Auto-migrations를 테스트하기 위해서는 MigrationTestHelper 테스트 룰을 사용하여 helper.runMigrationsAndValidate() 함수를 호출한다. (자세히 보기)

결론

Auto-migration 기능은 DB 스키마 변경에 쉽게 대응할 수 있도록 도와준다. @Database 내부에 autoMigration 파라미터를 추가하여 대부분의 기본적인 변경사항을 Room 자체적으로 핸들링할 수 있다. 테이블, 컬럼의 삭제나 리네임의 경우 AutoMigrationSpec을 구현해야 하며 나머지 경우는 기존의 Migrations을 사용해야 한다.

 

 

원문보기: Room auto-migrations

안드로이드 시스템 아키텍처

Android Open Source Project(AOSP)의 시스템 아키텍처는 다음과 같은 요소들의 계층 구조로 이루어져 있다.

   

어플리케이션 프레임워크

API를 통해 제공되는 안드로이드의 전체 feature-set이다. 어플리케이션 개발자가 주로 사용하는 API를 말한다.

ActivityManager, WindowManager, ContentProvider 등의 시스템 앱 및 서비스들이 여기에 포함된다.

 

사용자 어플리케이션 수준에서는 단순 API 호출로 보이지만, HAL 인터페이스에 직접 매핑되는 API들이 다수 존재한다.

바인더 IPC

바인더 IPC(Inter Process Communication)는 어플리케이션 프레임워크와 시스템 서비스를 연결해주는 인터페이스로, IPC를 통해 어플리케이션 레벨에서 시스템 서비스가 제공하는 기능을 사용할 수 있게 해준다.

하드웨어 추상화 계층(HAL: Hardware Abstraction Layer)

HAL은 하드웨어들을 연동하기 위한 표준 인터페이스이다. 하드웨어 공급업체들에게 이 인터페이스의 구현을 강제함으로써 모델별로 달라질 수 있는 제조업체의 하위 수준 소프트웨어를 안드로이드 프레임워크로부터 분리할 수 있다.

 

HAL은 여러 개의 라이브러리 모듈로 구성되어 있다. 각 모듈은 하드웨어 타입(ex. camera, bluetooth)에 맞는 인터페이스를 구현하고 있으며, 프레임워크 API가 호출되면 안드로이드 시스템은 해당 모듈을 로드하여 기능을 사용할 수 있게 해준다.

 

리눅스 커널

안드로이드 런타임(ART)은 리눅스 커널 기반으로 동작한다. 안드로이드에서 사용하는 커널에는 모바일 환경을 위한 LMK(Low Memory Killer), Wake lock 등의 몇몇 추가 기능이 포함되어 있다.

 

리눅스 커널을 사용하는 이점으로는 우선 안정성이 보장되고, 잘 알려진 커널이기 때문에 하드웨어 공급업체들이 하드웨어 드라이버를 개발하기 용이하다는 점을 들 수 있다.

참고

  1. Android Architecture
  2. Platform Architecture

안드로이드의 화면은 아래와 같은 단위로 구성된다. 화면을 구성하는 최소 단위는 View이며 최대 단위는 Window이다.

Window > Surface > Canvas > View

Window

Window는 화면 구성의 가장 상위 요소로 무언가를 그릴 수 있는 화면상의 사각 영역을 말한다.

하나의 화면 안에는 여러 개의 Window가 존재할 수 있고, 각각의 Window는 용도에 따라 고유한 타입을 가진다. 이들은 WindowManager에 의해 관리된다.

어플리케이션은 WindowManager와 상호작용하여 Window를 만들 수 있다. WindowManager는 각각의 Window에 Surface를 만들어 어플리케이션에 전달하고, 어플리케이션은 이를 통해 화면을 렌더링한다.

Window는 터치 이벤트, 키 이벤트 등 사용자 이벤트를 받아서 처리할 수 있다.

Surface

Surface는 화면에 합성되는 픽셀을 보유한 객체이다.

화면에 보여지는 모든 윈도우(스테이터스 바, 다이얼로그, 액티비티 등)는 자신만의 Surface를 가지고 있으며, Surface Flinger가 각 Surface의 픽셀들을 Z-order에 따라 합성하여 실제 화면에 렌더링하는 역할을 맡고 있다.

Surface는 렌더링시에 더블 버퍼링 방식을 이용하기 위해 일반적으로 두개의 버퍼를 가지고 있다.

Canvas

Canvas는 모든 드로잉 메서드를 포함하고 있는 클래스이다. 각종 도형, 선 등을 그리기 위한 모든 로직이 Canvas 내에 포함되어 있다. Canvas는 Bitmap 또는 OpenGL 컨테이너 위에 그려진다.

View

View는 Window 내에 존재하는 인터랙티브한 UI 요소를 말한다. (Button, TextView 등)

화면이 그려지는 방식

Surface가 잠겨있는 상태

Surface의 Canvas를 가져와 그리기에 사용한다. 계층 구조에 따라 View로 Canvas를 전달(onDraw)하여 각 View에 해당하는 UI를 그려나간다.

Canvas 잠금 해제 및 Post

모든 View가 버퍼에 그려지면 Surface가 잠금 해제되면서 현재 버퍼와 포그라운드 버퍼가 스왑되고 Surface Flinger에 의해 화면에 합성된다.

View가 그려지는 방식

View는 포커스를 얻으면 레이아웃을 그리도록 요청한다. 이 때, 레이아웃의 루트 노드를 제공해야 한다. 그리기는 루트 노드로부터 자식 노드 방향으로 트리를 따라 전위 순회 방식으로 그려진다. 레이아웃을 그리는 과정은 아래와 같다.

measure() -> onMeasure() -> layout() -> onLayout() -> draw() -> onDraw()

measure, onMeasure

View의 크기를 측정하기 위해 호출된다. 측정 과정에서 부모와 자식 View 간의 크기 정보를 정의하기 위해 아래 두 가지의 클래스를 사용한다.

  1. ViewGroup.LayoutParams
  2. 자식 View가 어떻게 측정될지를 요청하는데 사용된다.
    ViewGroup의 하위 클래스에 따라 다른 속성의 LayoutParams가 존재할 수 있다. (ex. LinearLayout.LayoutParams)
  3. ViewGroup.MeasureSpec
    • UNSPECIFIED: 자식 View가 원하는 대로 크기를 결정
    • EXACTLY: 부모 View가 자식 View의 크기를 결정
    • AT_MOST: 지정된 크기까지 자식 View가 원하는 대로 크기를 결정
  4. 부모 View가 자식 View에게 요구사항을 전달하는데 사용된다.

layout, onLayout

View의 크기와 위치를 할당하기 위해 호출된다.

draw, onDraw

실제로 View를 그리는 단계이다. 전달받은 Canvas 객체와 Paint 객체를 이용하여 필요한 모양과 색을 그린다.

참고

  1. Android - Window, View, Surface, Canvas, Bitmap
  2. [안드로이드] Window, Surface, Canvas, View
  3. [Android] View 함수

'개발 > Android' 카테고리의 다른 글

[번역] Room auto-migrations  (0) 2021.06.02
안드로이드 시스템 아키텍처  (0) 2021.04.24
안드로이드 아키텍처: MVVM  (0) 2021.04.05
안드로이드 아키텍처: MVP  (0) 2021.03.03
안드로이드 아키텍처: MVC  (0) 2021.02.24

MVVM 패턴이란?

MVVM 패턴은 앱의 구성 요소를 Model, View, ViewModel로 나눈 아키텍처를 말한다. View를 비즈니스 로직으로부터 분리시키고, ViewModel에서 디스플레이 로직을 제외한 대부분의 것들을 처리하는 방식으로 관심사를 분리하여 각 계층 간의 의존성을 최소화하는 방법이다.

MVVM, MVP, MVC architecture

MVC, MVP 패턴과 비교했을 때, MVVM은 각 계층간에 양방향 의존이 없는 것을 확인할 수 있다. ViewModel은 View의 존재를 모르고 View와 무관하게 동작하기 때문에 View가 다른 View로 대체되어도 사이드 이펙트가 발생하지 않는다. 마찬가지로 View 또한 ViewModel의 존재를 모르기 때문에 어떤 ViewModel에서든 사용될 수 있다.

안드로이드에서의 MVVM 패턴

아래는 이해를 돕기 위해 EditText에 검색어를 입력하고 Button을 누르면 해당 검색어로 검색한 결과를 출력해주는 안드로이드 앱이 있다고 가정하고 Model, View, ViewModel에서 하는 역할을 간단한 코드로 작성한 결과이다.

 

예시 코드에서는 View와 ViewModel의 중개자 역할로 RxJava의 Observable을 이용했다. ViewModel은 Model로부터 가져온 데이터를 Observable::onNext를 통해 발행하고, View는 해당 Observable을 구독해서 데이터를 처리하게 된다.

View

// 사용자 입력 ViewModel로 전달
button.setOnClickListener({
    showProgress()
    viewModel.findAddress(editText.text.toString())
})
// Observable 구독을 통한 결과 처리
viewModel.resultListObservable.subscribe({
    hideProgress()
    updateList(it)
})

ViewModel

fun findAddress(address: String) {
    model.fetchAddress(address)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            onSuccess = { entityList ->
                resultListObservable.onNext(entityList)
            },
            onError = { e ->
                resultListErrorObservable.onNext(e as HttpException)
            }
        )
        .addTo(disposable)
}

Model

fun fetchAddress(address: String): Single<List<Entity>> {
    return getRetrofit().fetchAddressFromServer(address)
}

MVVM 패턴의 장단점

MVVM 패턴은 MVP 패턴과 비교해 많은 이점을 갖는다. 각 계층의 관심사 분리가 확실해질 뿐만 아니라 유지보수성, 확장성, 테스트 용이성도 뛰어나다. 또한, View-ViewModel, ViewModel-View 사이의 상호 의존이 사라지면서 프로젝트 규모가 커질 경우, MVP보다 작성해야 할 코드의 양이 감소한다. RxJava/Android, Eventbus, LiveData 등을 이용한 Event-driven 방식의 처리에 익숙하다면 앱을 설계, 개발할 때 가장 유리한 구조가 아닐까 싶다.

MVP 패턴이란?

MVP 패턴은 앱의 구성 요소를 Model, View, Presenter로 나눈 아키텍처를 말한다.

이전 포스팅에서 MVC는 Controller가 Model의 데이터를 View로 직접 전달하지 못하고, View가 직접 Model에 데이터를 요청해야 한다는 단점이 있었는데, 이를 개선한 것이 MVP이다. MVP 패턴의 Presenter는 View와 Model 사이에서 Controller보다 좀 더 나은 중재자 역할을 수행한다.

 

MVP architecture

 

위 그림과 같이 Presenter는 View에서 요청한 입력에 대한 결과를 Model로 부터 가져와 다시 View에 전달해 줌으로써 View와 Model의 의존 관계를 제거하고, View는 오로지 Presenter가 전달해 준 데이터를 어떻게 표시할지에 대해서만 다룰 수 있게 된다.

안드로이드에서의 MVP 패턴

아래는 이해를 돕기 위해 EditText에 검색어를 입력하고 Button을 누르면 해당 검색어로 검색한 결과를 출력해주는 안드로이드 앱이 있다고 가정하고 Model, View, Controller에서 하는 역할을 간단한 코드로 작성한 결과이다.

View

// 사용자 입력 Presenter로 전달
button.setOnClickListener({
    presenter.findAddress(editText.text.toString())
    controller.findAddress(editText.text.toString())
})

Presenter

fun findAddress(address: String) {
    model.fetchAddress(address)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            onStart = {
                view.showProgress()
            },
            onSuccess = { entityList ->
                view.hideProgress()
                view.updateList(entityList)
            },
            onError = { e ->
                view.hideProgress()
                view.showErrorMessage(e.message)
            }
        )
        .addTo(disposable)
}

Model

fun fetchAddress(address: String): Single<List<Entity>> {
    return getRetrofit().fetchAddressFromServer(address)
}

MVC 패턴과 비교하여 동일한 동작을 MVP로 구현했을 때, 아래와 같은 이점이 있음을 확인할 수 있다.

  • View가 Presenter에만 의존적이다.
    MVC에서는 View가 Controller와 Model을 모두 알고있어야 했다.
  • Model의 역할이 줄어들었다.
    Model은 API를 호출하고 데이터를 반환하는 역할만 한다.
    UI 로직, 스레드 관련 로직들은 모두 Presenter에서 다룬다.

MVP 패턴의 장단점

장점: 테스트, 모듈화를 쉽게 할 수 있다.

단점: 많은 양의 보일러플레이트 코드가 생긴다. (View의 생명주기에 따른 네트워크 요청 취소 처리 등..)

MVC 패턴이란?

전통적인 아키텍처 패턴으로 UI를 가지는 앱을 개발하기 위해 앱의 구성 요소를 Model-View-Controller(MVC)로 나누는 방법이다. 하지만, MVC 패턴이 가지는 명확한 한계점 때문에 MVP나 MVVM 패턴에 비해 자주 사용되지는 않는다.

 

MVC에서 View는 데이터를 UI에 보여주는 로직만을 알고 있다. 사용자 입력에 반응하여 View는 Controller에게 입력을 전달하고 Controller는 입력에 알맞은 데이터를 Model에게 요청한다. 데이터가 준비되면 Controller는 View에게 이 사실을 알리고 View는 Model로부터 필요한 데이터를 가져와 UI에 표시해 준다.

 

이러한 흐름은 아래와 같이 표시할 수 있다.

MVC architecture

MVC는 View가 Model의 데이터가 사용 가능 여부를 판단하는 방식에 따라 두 종류로 나뉜다.

  • Passive MVC: Controller가 View에게 Model의 데이터가 갱신되었음을 알림
  • Active MVC: View가 직접 Model의 Observable 데이터를 구독함으로써 데이터 갱신을 처리함

안드로이드에서의 MVC 패턴

아래는 이해를 돕기 위해 EditText에 검색어를 입력하고 Button을 누르면 해당 검색어로 검색한 결과를 출력해 주는 안드로이드 앱이 있다고 가정하고 Model, View, Controller에서 하는 역할을 간단한 코드로 작성한 결과이다.

 

구현을 시작하게 되면 'Activity? Fragment? 어떤 안드로이드 컴포넌트를 View로 사용해야 하지?'라는 고민을 하게 될 수도 있다. View는 UI를 표시하고 유저와 상호작용을 하는 역할을 하므로 Activity, Fragment 모두를 View로써 사용할 수 있다.

View

// 사용자 입력 Controller로 전달
button.setOnClickListener({
    controller.findAddress(editText.text.toString())
})

Controller

fun findAddress(address: String) {
    view.showProgress()
    model.findAddress(address)
}

fun doWhenResultReady() {
    view.hideProgress()
    view.showResult()
}

fun doWhenThereIsErrorFetchingTheResult() {
    view.hideProgress()
    view.showError()
}

Model

fun findAddress(address: String) {
    fetchAddress(address)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            onSuccess = { entityList ->
                list = entityList
                controller.doWhenResultReady()
            },
            onError = {
                controller.doWhenThereIsErrorFetchingTheResult()
            }
        )
        .addTo(disposable)
}

MVC 패턴의 장단점

MVC 패턴을 도입하게 되면 테스트 용이성, 유지 보수성, 확장성이 증가하고 SOLID 원칙 중 단일 책임 원칙을 만족시킬 수 있다는 장점이 있지만 아래와 같은 단점 또한 존재한다.

  1. View가 Controller, Model 모두에 의존적이다.
    UI에 필요한 데이터를 표시하기 위해 View는 Controller에 데이터를 요청해야 하고, 준비 완료된 데이터를 Model로부터 받아와야 한다. 이는 View가 Controller, Model 모두를 알고 있어야 함을 의미한다.
  2. Model의 역할이 너무 광범위하다.
    위의 예시 코드를 보면 Model은 단순 데이터를 만드는 것뿐만 아니라 네트워크 연결, 컨트롤러로의 알림 역할까지 하고 있다. 앱이 비활성화되거나 화면 전환 등이 일어날 경우, Model에서 데이터 요청 작업에 대한 취소 처리까지 해야 함을 의미한다.
  3. View가 UI 로직으로부터 자유롭지 못하다.
    MVC에서는 View가 요청한 결과 데이터를 '어떻게' 보여줄지를 결정한다. View는 UI에 데이터를 보여주고 사용자 입력을 받는 역할만 해야 하는데 '어떻게'에 대한 로직이 포함되므로 좋은 구조라고 할 수 없다. 그렇다고 이 로직을 이미 너무 많은 역할을 하고 있는 Model에 넘길 수도 없다.

결론

MVC 패턴을 사용하면 아키텍처가 없는 것보다 테스트 용이성, 유지보수성, 확장성이 증가하지만 Controller가 View에 표시할 데이터를 직접 전달해 주지 않기 때문에, Controller가 View에 데이터 준비 완료를 알려주면 View가 직접 Model에서 데이터를 읽어와야 하는 구조적 한계점이 있다. 또한, View와 Model이 너무 많은 역할을 맡게 된다는 문제점이 있기 때문에 MVP, MVVM 패턴에 비해 자주 사용되지는 않는다.

맥 OS를 모하비(v10.14.x)에서 빅 서(v11.0.1)로 업데이트 후

VSCode(v1.51.1)에서 터미널 실행 시 0.5~1초 정도의 딜레이가 생겼다.

 

약간의 딜레이지만 터미널을 자주 사용하던 입장에서 매우 큰 답답함으로 느껴져서

구글링 해본 결과 터미널에서 아래의 커맨드를 입력해주면 대부분 해결되는 듯 하다.

 

codesign --remove-signature /Applications/Visual\ Studio\ Code.app/Contents/Frameworks/Code\ Helper\ \(Renderer\).app

 

출처: GitHub VSCode Issues

 

 오늘은 무더운 여름 별미로 먹기 좋은


메밀 막국수 맛집 '봉평 메밀마을'을 소개합니다.


동네에 막국수 집을 찾다가 우연히 발견하게 되었는데


벌써 세 번이나 방문하게 되었네요.


포스팅 할 계획은 없었기 때문에


사진은 많이 찍어두지 못했습니다.


참고하고 읽어주세요ㅜㅜ


외관 & 내부 모습

 


깔끔한 가게 외부 모습입니다.


매장 내부는 넓지 않지만


많이 붐비지 않아 조용하게 식사하기 좋습니다.



가게 한 켠엔 셀프바가 마련되어 있습니다.


가위와 앞접시, 배추김치, 열무김치,


냉/온 메밀차를 직접 필요한 만큼 가져다 먹을 수 있습니다.



메뉴 & 가격 정보

 


가장 중요한 가격!


모든 메뉴가 8,000원 이하로 매우 저렴합니다.


또한, 전 메뉴 1,000원 추가 시 곱배기로 주문이 가능하고


왕만두나 전병도 1인분씩 주문이 가능해 


부담없이 먹을 수 있었습니다.


주문 메뉴


냉메밀막국수, 메밀왕만두, 메밀전병, 메밀짜장면 등을 먹어봤지만


항상 허겁지겁 먹기 바빴기에...


음식 사진이 얼마 없네요ㅜㅜ


일반적인 성인 남자 기준 막국수 곱배기에 만두나 전병 1인분을 주문하면


배부르게 먹을 수 있었습니다.


< 냉메밀막국수 >


< 메밀왕만두 >


< 메밀전병 >


후기

 

냉면, 막국수, 밀면 류를 좋아하시는 분들이라면


정말 맛있게 드실 수 있을 것 같습니다.


메밀 특유의 쫀득쫀득한 식감이 정말 좋아요.


아쉬운 점을 꼽자면 제 입맛에는 김치가 너무 시고 (특히 열무)


메밀 짜장면은 조금 싱거웠습니다.


★★★★

★★★★

가격 ★★★★★

서비스 ★★★★☆


위치 및 추가 정보


북인천중학교, 서해그린아파트 맞은편에 위치해 있습니다. 


동네 주민이시라면 어렵지 않게 찾을 수 있어요.



주소 인천광역시 계양구 계산로 19

계양구 계산동 978-8번지

영업시간 오전 10시 ~ 오후 10시

전화번호 032-541-7732




[계산동/경인교대입구역]의 다른 맛집 보기


케이크가 맛있는 베이킹카페 322m





안녕하세요.


오늘은 특유의 깔끔함과 고급스러움 덕분에


정장벨트로 인기가 많은 몽블랑 벨트 38157을 리뷰해 보겠습니다.


< 영화 '더 테러 라이브' 中 >


이 벨트는 38157이라는 모델명 대신 하정우 벨트라고도 많이 불리는데요,


2013년에 개봉한 영화 '더 테러 라이브'에서 배우 하정우씨가 매고 나와서


유명해졌기 때문이죠.



 

군더더기 없는 포장입니다.


겉에 싸여있는 종이 포장을 열면 벨트가 들어있는 검정색 박스가 나옵니다.


선물 받은 제품이지만 리뷰를 위해 가격을 알아보았습니다.


인터넷 최저가 13~27만원, 백화점 정가 27~35만원 선에 판매되고 있습니다.


해외 여행 시, 인터넷면세점에서 적립금 신공을 사용하면 


20만원 내외로 구매하실 수 있습니다.


 

구성품입니다. 


가죽 서비스 가이드와, 보증서(?) 같은게 들어있고


더스트백에 오늘의 주인공인 벨트가 담겨있습니다.


사진상에는 검정색밖에 안보이지만 검정, 갈색 두 가지 색의 벨트 고리도


포함되어 있습니다.

 

  

벨트 근접샷입니다.


손자국좀 닦고 찍을 걸 그랬네요..하하


이전에 사용하던 빈폴 벨트와 비교했을 때


가죽이 훨씬 부드럽고 마감도 깔끔합니다!


고급스러워서 사회 초년생 or 2~30대 직장인 벨트로 딱인 것 같습니다.



 

벨트는 한 면은 검은색, 다른 한 면은 갈색을 띠고 있습니다.


그렇기 때문에 필요에 따라 버클만 돌려 끼워주면 양면으로 활용이 가능합니다.

 

 

이제 실착을 위해 벨트를 잘라줍시다.


벨트 뒷면의 빨간 동그라미 부분을 통해 버클 탈부착이 가능합니다.

 

 

몽블랑 각인이 아까운 느낌이지만...


착용을 위해서 원하는 길이만큼 남기고 가위로 과감히 잘라줍니다.



제 허리에 맞춰 자른 후 벨트를 채운 모습입니다.


제 허리가 30인치정도 되는데 한 뼘 이상을 잘라냈네요.


이제 한 번 착용해보겠습니다.

 

 

착용샷을 위해 정장을 입어야하나 고민하던 찰나에


마침 정장입을 일이 생겨서 다크네이비 정장에 착용해보았습니다.


역시 심플한 디자인답게 정장과 아주 보기 좋게 어울리네요.


인기 있는 벨트답게 만족도가 굉장히 높은 벨트인 것 같습니다.

 

 

오늘의 맛집은 경인교대 정문 앞에 위치하고 있는


분위기 좋은 베이킹 카페 '322m'입니다!


지난 달에 새로 오픈한 카페인데요,


특이한 점은 케이크를 매일매일 직접 만드는 베이킹 카페라는 점입니다.


예쁜 카페가 없던 저희 동네에 이런 카페가 하나 둘 늘어가는 것 같아 기분이 좋습니다.


자 이제, 아래에서 사진과 함께 하나씩 소개해드리도록 하겠습니다.



카페 외관 & 내부 모습

 


카페 입구 모습입니다.


건물이 조금 낡아 겉으로는 일반적인 작은 카페의 모습입니다.


하지만 안으로 들어가보면 내부 공간이 생각보다 넓고 아늑합니다.




 

입구 쪽에는 벽면을 따라 2~4인용 테이블이 위치해있습니다.


조금 더 들어가면 단체석 테이블도 있습니다.

 



 

카페 한 쪽에는 방처럼 독립된 공간도 마련되어있습니다.


2인용 테이블로만 구성된 이 공간은 테이블마다 조명과 콘센트가 있어


책을 읽거나 공부하기도 좋아보였습니다.


단체석과도 벽으로 막혀있어서 조용합니다.



깔끔한 화이트 톤의 카운터 모습입니다.


 아래에 진열된 케이크들이 디저트를 안 먹을 수 없게 만듭니다..


다이어트 하시는 분들은 피하셔야 될지도 모릅니다.



메뉴 & 가격 정보

 


커피 가격은 평범합니다.


싸지도 비싸지도 않은 맞은편 이디야와 비슷한 가격대입니다.


디저트류는 수제임에도 불구하고 저렴한 가격에 판매하고 있습니다.


 

주문 메뉴



< 아메리카노 & 밀크케이크 >


첫 방문의 이유였던 밀크케이크!


통밀케이크라 그런지 제가 생각한 식감과는 조금 달랐지만


촉촉하고 맛있었습니다.



< 핫초코 & 얼그레이 컵케이크 >


마시멜로가 들어간 진한 핫초코!


홀더 대신 컵을 두겹으로 사용해서 뜨겁지 않아요.


얼그레이 컵케이크도 크림이 느끼하지 않고 맛있었습니다.


하지만 생각보다 퍽퍽해서 조금 아쉬웠습니다.




< 크렌베리 & 초코칩 스콘 >


이 날 노트북을 가져가서 조금 오래있었는데 


마감 시간즈음 먹어보라고 주셨던 스콘ㅋㅋㅋㅋ


배고파서 정말 맛있게 먹었습니다.


저는 컵케이크보다 이게 더 맛있었어요.

 


후기


< 장점 >

1. 분위기가 좋음

2. 맛있는 수제 디저트류

3. 깔끔한 화장실

4. 테이블마다 있는 조명과 콘센트


< 단점 >

1. 장점들 때문에 사람이 많아지는 중




경인교대역 근처에 사신다면 한 번쯤 가보시는 걸 추천합니다!


저는 카페에 공부하거나 책을 읽으러 자주 가는편인데 


맞은편 이디야는 너무 좁고, 정문 앞 카페글렌은 너무 어두워요..


테이블 마다 있는 조명 + 콘센트 때문에


앞으로 자주 방문하게 될 것 같습니다.



위치


경인교대 정문 앞 골목에 바로 위치해있습니다.


경인교대입구역 4, 5번 출구에서 교대 방향으로 올라오시면 됩니다.





[계산동/경인교대입구역]의 다른 맛집 보기


여름 별미 막국수 맛집 '봉평 메밀마당'



+ Recent posts