mike-neckのブログ

Java or Groovy or Swift or Golang

Swift愛好会 vol39 に行ってきた

表題の通り、 Swift 愛好会 なる勉強会に行ってきました

f:id:mike_neck:20180609045321p:plain

love-swift.connpass.com

ついでなので、 swift-log について発表してきた

www.slideshare.net

雑に紹介すると、 swift-log は apple が作っている slf4j みたいなもの。 slf4j が log4j などのログを奪う機能(slf4j-over-log4j)はまだない。

勉強会の雰囲気は、アットホームなノリで、肩肘張らずにいられる感じ。

普段はエンジニアでない方の iOS アプリ開発の疑問も、みなでどうすればいいか意見を出し合う相談会になった。

初心者も上級者も誰でもウェルカムだそうですので、 swift さわってる人は一度行ってみるとよいかもしれません。

pixela-java-client のバージョン 0.3 を maven central repository にリリースしました

なるべく 月1度リリースしようと心がけてはいますが、ちょっと時間が経ってしまいました。

ということで、 pixela-java-client のバージョン0.3 を maven central repository にリリースしました。

https://search.maven.org/search?q=pixela

github.com

https://pixe.la/v1/users/pixela-java-client/graphs/run-ci?mode=short

↑ CI 回している回数を草化した画像


今回の変更点は以下のとおりです。

  • 内部 http-client が返す型に Response という型を使っていましたが、これは実質 Mono であるし、これを表現することで何らかの問題を解決しているわけではないので、なくしました
  • Pixela から Graph 既存の Graph に移動する際に、 pixela#graph に与えるパラメーターが GraphId でしたが、これのインスタンスを作るのもまあ面倒なので、 String で与えられるようにしました。内部的には GraphId によるバリデーションを行っていますが、ユーザーには極力簡単な API を提供したほうがよいだろうと考えてこの形にしました。

今後

  • Android から使えないのがもったいないので、なんとか Android でも使えそうな型にラップして提供するのはありかと思っているので、そうしたい
  • Java8 からも使えるほうがよろしいかと思うので、 reactor-netty で実装する PR 作っているけど、その過程で、今回の Response の型とかの変なところに気がついて修正しました

現場からは以上です


c.f. 今 Swift 用の pixela client を書いています。こちらもあわせてよろしくおねがいします!

github.com

クリーンアーキテクチャーの読書メモ(10)

f:id:mike_neck:20190312230053p:plain

第四部 コンポーネントの原則

  • SOLID 原則 = レンガを組み合わせて部屋を作る
  • コンポーネントの原則 = 部屋を組み合わせて建物を作る

§12 コンポーネント

  • コンポーネント = デプロイの単位
  • コンポーネントの歴史
    • アプリケーションと関数ライブラリーのアドレス(セグメンテーション)を分けてた
      • 関数ライブラリーのアドレス開始位置が固定なので、アプリケーション領域が増えると、また先にある特定の固定アドレスにアプリケーションの続きを配置する
    • リロケータビリティ/リンクローダー
      • コンパイラーが出力するバイナリに手を加えてスマートローダーでメモリに再配置
      • コンパイラーも関数名をメタデータとして関数名を出力
      • ライブラリーの関数も外部参照として扱えるようになる
    • リンカ
      • プログラムが巨大化してリンクローダーが使い物にならなくなった
      • ロードとリンクに分けて、後者をさらに別のアプリケーション(リンカ)に切り出し
    • プログラムの巨大化とマシンの高速化の戦い
      • マシン性能の向上によるリンク時間短縮が勝った
  • つまり

Spring WebFlux + r2dbc + Kotlin でデータベーストランザクション

f:id:mike_neck:20171113232305p:plain

前回 ReactiveCrudRepository を使ったアプリケーションの話をしたが、残念なことに今の所トランザクションを指定できないので、アプリケーションを終了したら、データはなくなるし、複数のテーブルへの操作をアトミックな操作として指定できないことをメモしておいた。

mike-neck.hatenadiary.com

では、トランザクションをどうおこなうかだが、 TransactionalDatabaseClient を使うようである。

Configuration

TransactionalDatabaseClient を作るには ConnectionFactoryインスタンスTransactionalDatabaseClient#create にわたすだけでよい。そうして作ったインスタンスビーン登録する

@Configuration
@EnableR2dbcRepositories
class DatabaseConfiguration : AbstractR2dbcConfiguration() {

  @Bean
  override fun connectionFactory(): ConnectionFactory =
      ConnectionFactoryOptions.builder()
          .option(ConnectionFactoryOptions.DRIVER, "postgresql")
          .option(ConnectionFactoryOptions.HOST, "localhost")
          .option(ConnectionFactoryOptions.PORT, 5432)
          .option(ConnectionFactoryOptions.USER, "postgres-user")
          .option(ConnectionFactoryOptions.PASSWORD, "postgres-pass")
          .option(ConnectionFactoryOptions.DATABASE, "postgres")
          .build()
          .let { ConnectionFactories.get(it) }

  @Bean
  fun transactionalDatabaseClient(connectionFactory: ConnectionFactory): TransactionalDatabaseClient =
      TransactionalDatabaseClient.create(connectionFactory)
}

作った TransactionalDatabaseClient@Repository 相当のクラスで使うのが良いと思われる。

@Repository
class UserTransactionalRepository(private val dbClient: TransactionalDatabaseClient) {

  fun saveUser(user: User, password: UserPassword): Mono<Unit> =
      dbClient.inTransaction { db ->
        db.execute()
            //language=sql
            .sql("insert into users(id, name, created) values($1, $2, $3)")
            .bind("$1", user.id)
            .bind("$1", user.name)
            .bind("$1", user.created)
            .fetch()
            .rowsUpdated()
            .thenReturn(Unit)
            .flatMap {
              db.execute()
              //language=sql
                  .sql("insert into user_password(id, password_hash, created) values($1, $2, $3, $4)")
                  .bind("$1", user.id)
                  .bind("$2", password.hash)
                  .bind("$3", user.created)
                  .fetch()
                  .rowsUpdated()
            } // flatMap
            .thenReturn(Unit)
      } // inTransaction
      .last()
}
  • TransactionalDatabaseClient#inTransaction にわたす関数の中で、データベースへの操作を行っていく
  • 複数のデータベースの操作は前の操作の結果に依存するので(外部参照などの都合による)、失敗した/成功したの状態を引き継ぐために、 Monothen などで引き継いで行く(db.execute() 〜 はあくまで操作の予約なので、実際に実行されるのは subscribe されたあとになる)

実行するクエリによっては、この saveUser 関数の中はいるに耐えないものになるので、 Repository の更に下のレイヤーを作る必要があるように思われる。


以下、ゴミ

なお、前回の続きのようなアプリケーションの続きではこのあたりを抽象的に扱って、次のようなコードになるようにしている。

// サービスクラスの一部
unitOfWorkFactory.unitOfWork(
        userTransactionalRepository.countUserByName(user.name).condition { it == 0L },
        userTransactionalRepository.create(user),
        tokenTransactionalRepository.create(token))
        .thenReturn(user to userToken)

// UserTransactionalRepository の一部
RepositoryAction.create { db ->
  db.insert()
      .into(UserDb::class.java)
      .using(UserDb.from(user))
      .fetch()
      .rowsUpdated()
}

サンプルコードをそのうちに github にあげるつもりだけど、この休みの後半は時間がほとんど取れなかった…