2020年5-7月振り返り

5-7月の振り返り

5-7月の振り返りです。直近私生活で色々あり、ブログの更新が滞っしまいました。。。 5-7月は新しい会社に移って以降、下記のようなことをしていました。

  • iOSアプリ開発
  • DB論理設計
  • API Server実装
  • Pythonの実装修正
  • サービスの全体設計
  • 全体のスケジュール作成・チケット作成
  • セキュリティ

経験が薄いことや初めてのチャレンジが多く、とても大変でしたが自身の知見やスキルの向上が実感できた有意義な3ヶ月だったと感じています。

話は変わりまして、この振り返りですが目標の数を削減し、「読書」とブログ投稿のみ残そうかと思います。 日々の中でやるべきこと、やりたいことが増え、目標の達成に避ける時間が減ってきたことが主な理由です。 以下、目標の対象を限定した上での振り返りです。

プロダクト作成の基礎力向上

読書

「達人に学ぶDB設計 徹底指南書」を読み終えました。RDBに関するバッドプラクティスや論理的な方針、 実際に運用を前提とした上でのグレーノウハウの紹介など実務に応用できる内容が多かった印象です。 正規化周りの話は情報処理試験で知ってはいましたが、詳しい解説の中で微妙に理解が曖昧だったところもあり、 勉強になりました。

「Web API The good parts」を読みはじめました。Web APIを作成する上での実践的なプラクティスを学べたらと思います。

アウトプット

ブログ投稿

  • 年次目標 50(月次目標 約4)
  • 実績:月次(3ヶ月合計) 4記事 / 累計 14記事

本記事と合わせて4記事でした。 3ヶ月の合計なので1記事/月ですね。。。 もう少し書けるよう時間の使い方を考えます。

その他

活動

  • Coursera の Machine-Learningコースを完了しました。予想通り業務の合間に行うのは大変でしたが、なんとか終わることができほっとしています。
  • Flutterでのアプリ開発を本格的にはじめました。

読書

読書開始

読書中

読書完了

読書中止

【GitLab】GitLab内のProjectを複製する方法

はじめに

GitLab内で既存Projectを複製し新規Projectを作成する方法を記載します。

環境設定

以下の環境を使用しています。

  • GitLab

内容

複製元のProjectの操作

  • export projectを押下する f:id:Iganin:20200726145713p:plain

  • Project export started. ~~ のバナーが表示される f:id:Iganin:20200726145726p:plain

  • exportに成功するとdownload exportが選択できるようになる f:id:Iganin:20200726145814p:plain

複製先のProjectの操作

  • new projectを押下 f:id:Iganin:20200726150040p:plain

  • create project画面でimport projectを選択 f:id:Iganin:20200726150059p:plain

  • 作成するproject名称入力

  • exportした gzファイルを GitLab project exportの「ファイルを選択」から選択
  • import projectを押下 f:id:Iganin:20200726150212p:plain

  • projectのimport成功のバナー表示 f:id:Iganin:20200726150235p:plain

まとめ

projectのimport・exportはなかなか行う機会がないので、一度流れをおっておくといざ対応するタイミングで混乱しない気がします。

【iOS】PlaygroundでSwiftUIのViewを描画する

はじめに

SwiftUIで簡単なView構成を試したいときにわざわざProjectを作るのもめんどくさいなという時がありました。 Playgroundを使用してViewの画面を作成し、表示や動作を確認する方法がわかりましたのでメモがてら記載します。

環境設定

以下の環境を使用しています。

内容

Playground Supportをimportし PlaygroundPageを使用することでPlayground上で画面を描画することができます。 PlaygroundPage.current.setLiveView(ContentView())としてるのが設定箇所です。 UIHostingViewControllerを使用して、PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())とすることでも設定可能です。

コード例を下記に記載します。

import Combine
import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                Text("Sample1")
                Text("Sample2")
                Text("Sample3")
            }
        }.navigationBarTitle("Sample Page")
    }
}

// ここで画面描画のための設定を行なっている
PlaygroundPage.current.setLiveView(ContentView())

画面表示は下記となります。コードを実行することで動作確認等も行うことができます。 f:id:Iganin:20200518065527p:plain

まとめ

Playgroundは主に簡単なロジックの挙動確認やSwiftの仕様確認に使用していましたが、簡単な画面の作成に使用するのも良さそうです。

参考

【iOS】UIViewをUIImageに変換する

はじめに

UIViewをUIImageに変換するというよくあるやつです。 今だとこのやり方が良いのではないかというのが見つかったのでメモがてら記載します。

環境設定

以下の環境を使用しています。

内容

いかに作成したextensionメソッドを記載します。 UIGraphicsImageRendererを使用することでシンプルに書くことができます。

public extension UIView {
    func convertToImage() -> UIImage {
       let imageRenderer = UIGraphicsImageRenderer.init(size: bounds.size)
        return imageRenderer.image { context in
            layer.render(in: context.cgContext)
        }
    }
}

従来通りのよくあるやり方は下記です。 UIGraphicsBeginImageContextWithOptionsを使用することでUIImageを作成しています。 UIGraphicsBeginImageContextWithOptionsscaleに0.0以外を入力したり、 UIGraphicsBeginImageContextを使用したりすると画像がぼやけたりするので注意が必要です。 また、 UIGraphicsGetImageFromCurrentImageContext()の返却値がOptionalのため安全に書こうとするとメソッドの返却値が UIImage?となります。

public extension UIView {
    func convertToImage() -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
        guard let context = UIGraphicsGetCurrentContext() { return nil }
        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
}

まとめ

UIGraphicsImageRendererはiOS10で導入されたclassです。contextの制御やscaleの管理などをせずに描画を行うことができます。 OSのサポートバージョンが10.0以上でしたら是非使用してみてください。

参考

2020年4月振り返り

4月の振り返り

目標の進捗状況の定期確認です。5月に入って久しいですが。。。

4月はPostgresの取り扱い、サーバーサイド開発、 Docker + Docker Composeの習得など新しい分野にいろいろ挑戦しました。 新しいことを学ぶのはとても楽しいですが、反面締め切りがある中での新しいことへの挑戦はなかなか負荷がたかいなというのも正直感じているところです。

そうはいってもやるべきことはやるしかないので、引き続き気合をいれて頑張ります。まずは生き残らなければ。

データ構造とアルゴリズム

プログラミング問題

  • 変更後目標: 年間150問(Easy: 75問、 Medium 60問、 Hard 15問) - 月次 12.5問
  • 実績: 月次 5問 / 累計 41問

スタックの問題を中心に進めています。 なかなか時間をさけていないのが実情です。

世界で闘うプログラミング力を鍛える本 通読

  • 年次目標 通読・問題全問正解
  • 実績: Chapter3 - 2/6

Chapter3 のスタックとキューの問題を解いています。

プロダクト作成の基礎力向上

読書

進捗なしです。。。カフェに行かないとなかなか厳しい。。。

アウトプット

ブログ投稿

  • 年次目標 50(月次目標 約4)
  • 実績:月次 3記事 / 累計 10記事

本記事と合わせて3記事でした。Ktor + Exposeの話を書いています。

身体能力の強化

ウェイトトレーニング

Covid-19の感染防止のためジムを休会しました。 状況が落ち着くまで本目標の更新は停止とします。

体脂肪率 10%

  • 年次目標:体脂肪率を約 10% 以下
  • 実績: --%

上述のようにジムに通うことが困難になったことに伴い、本目標の月次更新も状況が落ち着くまで休止とします。

その他

活動

  • Weekly で購読している Kotlin Weekly, iOS Weekly, Swift Weekly, Android Weekly, Flutter Weeklyのメルマガから各3記事程度は目を通すことにしました
    • Pocketを使用してStock -> その週のうちにPick Upしたものを読み通す、でしばらく運用してみようと思います。
  • Point-FreeのSubscriptionをはじめました。Composable Architectureを理解したいです。
  • Coursera の Machine-Learningのコースをはじめました。
    • 思ったよりきつそうで挫けそうです。頑張ります。 www.coursera.org

読書

読書開始

読書中

読書完了

読書中止

【Kotlin】Exposedのテーブル定義からテーブルスキーマ生成のSQL Scriptを作る

はじめに

下記の記事の続きです。 iganin.hatenablog.com

Exposedによってテーブル定義を比較的簡単に作成することができます。 また、作成したテーブル定義から実際にテーブルスキーマを作成することも可能です。

ただ、実際にサービスを開発する際はServerのコード実行時にテーブルスキーマ生成を行うのではなく、 DBに事前に生成しておいたり、Dockerでのcontainer初回起動時に作成することが多いと思います。

そこで本稿では、作成したテーブル定義から実際にテーブルスキーマを作成する方法、 および作成時のConsoleログ出力からSQL scriptを作成する方法を記載します。

書くこと

  • Exposedを使用したテーブル定義からテーブルスキーマを作成する方法
  • テーブル作成の実行時コンソールログ出力からSQL scriptを作成する方法

書かないこと

  • Exposedの導入方法
  • データベースとの接続方法

環境設定

以下の環境を使用しています。

  • Exposed 0.23.1

内容

対象テーブル

以下のテーブルを対象とします。

object Companies: Table("companies") {
    val id = long("id").autoIncrement()
    val name = varchar("name", 255)
    val createdAt = datetime("created_at")
    val updatedAt = datetime("updated_at")
    val deletedAt = datetime("deleted_at").nullable()
    override val primaryKey = PrimaryKey(id, name = "pk_company_id")
}

object DepartmentsEmployees: Table("departments_employees") {
    val id = long("id").autoIncrement()
    val departmentId = long("department_id").references(Departments.id)
    val employeesId = long("employee_id").index().references(Employees.id)
    val createdAt = datetime("created_at")
    val updatedAt = datetime("updated_at")
    val deletedAt = datetime("deleted_at").nullable()
    override val primaryKey = PrimaryKey(id, name = "pk_departments_employees_id")
}

object Departments: Table("departments") {
    val id = long("id").autoIncrement()
    val companyId = long("company_id").references(Companies.id)
    val name = varchar("name", 255)
    val createdAt = datetime("created_at")
    val updatedAt = datetime("updated_at")
    val deletedAt = datetime("deleted_at").nullable()
    override val primaryKey = PrimaryKey(id, name = "pk_department_id")
}

object Employees: Table("employees") {
    val id = long("id").autoIncrement()
    val familyName = varchar("family_name", 255)
    val givenName = varchar("given_name", 255)
    val createdAt = datetime("created_at")
    val updatedAt = datetime("updated_at")
    val deletedAt = datetime("deleted_at").nullable()
    override val primaryKey = PrimaryKey(id, name = "pk_employee_id")
}

Schemaの作成・削除

SchemaUtilsを使用することで、テーブル定義から実際のテーブルスキーマを作成することができます。 SchemaUtils.drop(テーブル名)でテーブルスキーマの削除を、 SchemaUtils.createでテーブルスキーマの作成を行うことができます。 SchemaUtils.drop(テーブル名)の際は外部制約の依存関係を考慮した削除が必要になります。例えば、上記のテーブル例では、 CompaniesDepartmentsに参照されているため Departmentsを削除してからでなければ削除することはできません。

以下、実際のコードになります。

    transactions {
            // テーブルスキーマの削除
            SchemaUtils.drop(DepartmentsEmployees)
            SchemaUtils.drop(Departments)
            SchemaUtils.drop(Companies)
            SchemaUtils.drop(Employees)

           // テーブルスキーマの作成
            SchemaUtils.create(Companies)
            SchemaUtils.create(Employees)
            SchemaUtils.create(Departments)
            SchemaUtils.create(DepartmentsEmployees)
    }

実行されるSQL文の確認

SchemaUtils.drop実行時には実際は DROP TABLE IF EXITS table_nameというSQL文が実行されています。 addLogger(StdOutSqlLogger)を実行することでSQL文をConsoleの標準出力に表示させることができます。

例えば、先ほどのSchemaUtils.dropの実行結果は下記のように出力されます。

SQL: DROP TABLE IF EXISTS departments_employees
SQL: DROP TABLE IF EXISTS departments
SQL: DROP TABLE IF EXISTS companies
SQL: DROP TABLE IF EXISTS employees

ここからSQL scriptを作成するとします。SQL scriptを作成するに当たって、以下の対応が必要です。

  • SQL文以外のログの削除
  • SQL: の削除
  • 改行部分に ; を追加

上記を達成するために今回はCotEditorで正規表現を用いた置換処理を行います。

SQL Scriptの作成

SQL文以外のログの削除

CotEditorを開き、 command + Fなどで検索と置換画面を開きます。正規表現にチェックをつけ、 ^(?!.*XXX).*\nを入力します。 検索結果を空文字列に全て置換することで対応完了です。 f:id:Iganin:20200419102454p:plain

SQL: の削除

検索と置換画面SQL:と入力し、すべてから文字列に置換します。

改行部分に ; を追加

改行文字列(\n)を入力し、 ;\nに全て置換します。 f:id:Iganin:20200419102739p:plain

以上までで作成されたファイルを .sql拡張子付きで保存します。 これで SQL scriptの作成は完了です。実際に先ほどのテーブル定義と実行コードから作成されたSQL scriptは下記のようになります。

DROP TABLE IF EXISTS departments_employees;
DROP TABLE IF EXISTS departments;
DROP TABLE IF EXISTS companies;
DROP TABLE IF EXISTS employees;
CREATE TABLE IF NOT EXISTS companies (id BIGSERIAL, "name" VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP NULL, CONSTRAINT pk_company_id PRIMARY KEY (id));
CREATE TABLE IF NOT EXISTS employees (id BIGSERIAL, family_name VARCHAR(255) NOT NULL, given_name VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP NULL, CONSTRAINT pk_employee_id PRIMARY KEY (id));
CREATE TABLE IF NOT EXISTS departments (id BIGSERIAL, company_id BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP NULL, CONSTRAINT pk_department_id PRIMARY KEY (id), CONSTRAINT fk_departments_company_id_id FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE RESTRICT ON UPDATE RESTRICT);
CREATE TABLE IF NOT EXISTS departments_employees (id BIGSERIAL, department_id BIGINT NOT NULL, employee_id BIGINT NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP NULL, CONSTRAINT pk_departments_employees_id PRIMARY KEY (id), CONSTRAINT fk_departments_employees_department_id_id FOREIGN KEY (department_id) REFERENCES departments(id) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT fk_departments_employees_employee_id_id FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE RESTRICT ON UPDATE RESTRICT);
CREATE INDEX departments_employees_employee_id ON departments_employees (employee_id);

まとめ

Exposedを使用してテーブル定義から実際のテーブルスキーマを削除したり作成したりする方法と、その実行結果のConsole出力から SQL Scriptを作成する方法を記載しました。

PostgresのDocker実行などの場合、 /docker-entorypoint-initdb.dSQLファイルをおき、初期のテーブルスキーマ作成などを行うかと思います。 その際に使用するSQL scriptの作成などに本稿の方法がお役に立てれば幸いです。

参考

【Kotlin】Exposedでテーブル定義を実装する

はじめに

Server Side で Kotlinを使用する際に候補として上がるフレームワークとしてSpringとKtorがあります。 SpringはJavaの頃から親しまれているWeb Frameworkです。それに対し、Ktorは最近できたKotlinベースのWeb Frameworkであり、CoroutineなどKotlinの機能が活用されています。

Spring採用時に使用するORMとしてDoma2などが有名ですが、Ktorの場合はExposedがよく使われるようです。最近、ktor + Exposedで開発を行っており、Exposedを使用してテーブル定義を作成することがありましたので、備忘もかね記載します。

書くこと

  • Exposedを使用したテーブル定義

書かないこと

  • Exposedの導入方法
  • データベースとの接続方法
  • ※このあたりはすでに記事がいくつかあるためそちらをご参照ください。チュートリアルもわかりやすいです。

環境設定

以下の環境を使用しています。

  • Exposed 0.23.1

内容

使用するライブラリ

以下のライブラリを使用しています。

  • org.jetbrains.exposed: exposed-core
  • org.jetbrains.exposed: exposed-dao
  • org.jetbrains.exposed: exposed-jdbc
  • org.jetbrains.exposed: exposed-java-time ※1

  • ※1 Exposedのgetting started を見ると core, dao, jdbcのみしか記載がありません。後述しますが、生成日、更新日などをカラムとして持たせたい場合にdatetimeやdate、timestampを指定したいかと思います。それらはcore, dao, jdbcには含まれていません。そのため、 java-timeやjodatimeなどを別途implementationする必要があります。

テーブル定義例

UML

以下に簡単なテーブル定義の例を記載します。会社テーブルがあり、会社には1つ以上の部署が紐づきます。一つの部署には0人以上の従業員がおり、従業員は複数部署に所属する可能性があるとします。以下にPlantUMLで簡単に作成したER図を添付します。なお、主キーにはナチュラルキーではなくサロゲートキーを使用しています。また、従業員と部署の関係性は多対多のため中間テーブルを作成しています。

f:id:Iganin:20200413002501p:plain
ER Diagram

※本題とはそれますが、PlantUMLはER図などのUMLを書く際に非常に重宝しましたのでご存じない方はこの機会に一度調べてみてください。上記の図もPlantUMLで5分程度で作成しています。

実装

上記のテーブルを実際に実装してみます。まずはCompaniesテーブルを作成し概要をコメントで記載します。

// tableはobjectで定義します。 Table("name")のname部分にDBでのテーブルの名称を記載します。
object Companies: Table("companies") {
  // 型名("name")のname部分にテーブルでのカラム名を記載します。
  // .autoincrement()を使用すると生成時に自動的に1ずつincrementされます。
  val id = long("id").autoIncrement()
  // 文字列にはchar, varchar, textが使用できます。
  // varchar使用時には文字数の指定が必要です。
  val name = varchar("name", 255)
  // 生成日にdatetimeを使用しています。他にdate, timestampが使用できます。
  // dateはyyyy-MM-ddのように日付までのみ保持し、datetimeはyyyy-MM-dd HH:mm:ssSSSSSSを保持します。
  // .default()によって値が明示的に入力されなかった場合のデフォルト値が設定されます。下記の例では現在時刻が入ります。
  val createdAt = datetime("created_at").default(LocalDateTime.now())
  val updatedAt = datetime("updated_at").default(LocalDateTime.now())
  // 論理削除された日付を入力とします。
  // カラムの値は何も指定しないとnot nullとなりますが、 nullable()を付与することでnull許容となります。
  val deletedAt = datetime("deleted_at").nullable()

  // primaryKeyをoverrideすることでテーブルの主キーを決めることができます。
  override val primaryKey = PrimaryKey(id, name = "pk_company_id")
}

long("id")のように定義すると Column<T> 型の変数が生成されます。これがテーブルにおけるカラムに相当します。 ここでTableを使用しprimaryKeyを自身で定義していますが、 IntIdTableLongIdTableを継承すると EntityID<Int>EntityID<Long>をidとして持ち、主キーとして指定された状態のテーブルを生成できます。

他のテーブルの定義を記載していきます。created_at, updated_at, deleted_atは冗長となるため省略します。

object DepartmentsEmployees: Table("departments_employees") {
  val id = long("id").autoIncrement()
  // referencies()で外部キーを設定します。 fkName = ""  によって任意の名前を外部キーにつけることができます。
  // 外部キーのアップデート時、削除時の制約を明示的につけることができます。デフォルトは ReferenceOption.RESTRICTです。
  // 外部キー制約において外部キーとして参照されているキーを持つレコードが更新されたり削除された場合の挙動を示します。
  // 一例ですが、RESTRICTは外部キーとして参照されているレコードは外部キーの参照元のレコードが全てなくならない限り削除することができません。
  val departmentsId = long("department_id").index("idx_department_id").references(Departments.id, fkName = "fk_department_id", onUpdate = ReferenceOption.CASCADE, onDelete = ReferenceOption.RESTRICT)
  val employeesId = long("employee_id").index().references(Employees.id, fkName = "fk_emploee_id")

  override val primaryKey = PrimaryKey(id, name = "pk_departments_employees_id") 
}

object Departments: Table("departments") {
  val id = long("id").autoIncrement()
  // indexによってインデックスを生成することができます。index(name)のname部分を入力することで任意の名前を付与できます。
  // 外部キーを辿ってレコードを引っ張ることが多いため、一般的に外部キーにindexを貼るようです。(理解が間違っていましたらご指摘ください)
  val companyId = long("company_id").index("idx_company_id").references(Comapnies.id)

  val name = varchar("name", 255)
  override val primaryKey = PrimaryKey(id, name = "pk_department_id") 
}

object Employees: Table("emploees") {
  val id = long("id").autoIncrement()
  
  val familyName = varchar("family_name", 255)
  val givenName = varchar("given_name", 255)
  override val primaryKey = PrimaryKey(id, name = "pk_employee_id") 
}

以下まとめと上記で記載できなかったことです。

  • referencesで外部キーを付与します。 onUpdated, onDeletedで外部制約を明示的に指定できます。 (CASCADE, SET_NULL, RESTRICT, NO_ACTIONがあります)
  • indexでインデックスを作成します。uniqueIndex()とすることでユニーク制約をつけることができます。
  • (long("fkId").references("FkEmtity.id")).nullable() とすることで外部制約を付与しながらnull許容なカラムを生成できます。

以上雑多ではありますが、現状テーブル作成周りで学んだことを記載しました。

まとめ

Spring + Doma2など Doma2 をORMと使用することも検討しましたが、 Kotlinのサポートが実験的であるため不安がありました。またDaoのインターフェースをJavaで定義する必要があるようです。 Exposedは100% Kotlinで作成されており、プロジェクト全体をKotlinで統一したいと考えた際に非常に魅力的に映りました。 まだDML側の部分の理解が追いついていませんが、今のところ非常に直感的に書くことができ良いなというのが感想です。 サーバーサイドの開発をJVM言語で行う場合はKtor + Exposedは選択肢としてありなのではないでしょうか。

参考