おはようございます。フロントエンドチームの武井です。
今回はフロントエンドチーム最近取り組んでいるライブコーディングを行いましたので、そのときの気付きを記事にしようと思います。
ライブコーディングについてはマネージャーの山田が以前の記事ですこし触れていましたが、フロントエンドチーム内で実験的に有志がおこなっています。ライブコーディングがチーム内の文化として定着するかどうかはまだ未知数の状態です。
そういえば こんさんの花火ネタも元々ライブコーディングでやった内容だったような。
なぜフロントエンドチームでgoのライブコーディングをしたか
以前こちらで記事を書いたときには「サーバーサイドエンジニアの武井です」と名乗らせていただきましたが、2022年の10月よりフロントエンドチームに移籍しています。
今回フロントエンドチームのライブコーディングで何かを発表するにあたり、サーバーチームから移籍したばかりの自分がフロントエンドの猛者たちの前で何を発表したら有益だろうと考えました。
フロントエンドチームは普段はCMSのUIなどを作っており主にJavaScript(TypeScript)やNuxt.jsを使った開発業務を行っています。かたやサーバーチームはgo言語を使ってDDDの考えにもとづいた開発を行っています。フロントエンドチームでもgo言語やドメイン駆動開発(DDD)について多少なりとも興味はあるのではないでしょうか。そこで普段サーバーチームがどんな感じでコードを書いているかを実演してみることにします。
シナリオを考える
さて、ライブコーディングは初めてですので何をどうしたものやら。とりあえず1時間という枠は決まっているので、その中で短すぎず長すぎずという分量である必要があります。
今回はサーバーチームの作業紹介ということで、DBからデータを取得してgRPCに受け渡すまでの一連の流れのコーディングをやってみる事しました。ヤプリのサーバサイドのコードはDDDの考え方に基づきアプリケーション層、ドメイン層などの階層構造に分かれています。それだけでけっこうな分量になりそうなので、ライブコーディングでは簡単なデータ取得のロジックのみを実演することにしました。
業務で使用しない架空のテーブルからデータを取得するということにしました。事前に取得元テーブルのCREATE文のSQLと受け渡し先のProtocol Buffersの設定はあらかじめ作っておくことにしました。
事前に準備したSQLとprotobuf (クリックで開きます)
-- 架空の コミックスTABLE CREATE TABLE IF NOT EXISTS `comics` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(255) COLLATE utf8mb4_bin NOT NULL, `author` varchar(255) COLLATE utf8mb4_bin NOT NULL, `series` int(10) unsigned DEFAULT NULL, `publisher` varchar(255) COLLATE utf8mb4_bin NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) )ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
syntax = "proto3"; package yappli.cms.v2; option go_package = "yappli/cms/v2;cmspb"; option java_package = "yappli.cms.v2"; message ComicResource { int32 id = 1; string title = 2; string author = 3; int32 series = 4; string publisher = 5; } service ComicService { rpc GetComicResource(GetComicResourceRequest) returns (GetComicResourceResponse); } message GetComicResourceRequest { string id = 1; } message GetComicResourceResponse { ComicResource resource = 1; }
これを元にコードを記述していくことにします。ですがこれだけだとinterfaceを書いて実装を書いてmapperを書いての繰り返しでちょっと単調になりそうです。ライブですので、こんさんの花火ネタのようなハデさが欲しいところですがなかなかそうも行きません。
そこで、最近個人的に導入を試しているGithubのCopilotを使ったAIによる自動コーディングを小ネタとして取り入れてみます。VSCodeの拡張機能として導入しておきます。
準備、そして実演
準備として上記のシナリオ作成やネタ仕込みのほか、実際に発声しての予行演習も何回か行い発表の時間調整などを行いました。あまりに入念に準備されたライブコーディングは本当にライブ感はあるのかという疑問はありますが、ぐだぐだな発表になってしまうと観てもらう人に申し訳ありません。
こういったチーム内のイベントや準備もチームビルディングの一環として業務内に行う事が出来ます。ありがたいですね。
ライブコーディングはDiscordで行う事にしました。ヤプリでのミーティングはZoomを使うことが多いのですが一部Discordも併用しています。Discordは配信画質に「より読みやすいテキスト」という低速フレームレートのモードがあるのでライブ配信に適していると思いました。
発表は無事終わりました。すこしグダグダになりながらもなんとか乗り切れたと思います。
実際に作成したコード(クリックで開きます)
長いので作成したコードの一部のみ抜粋で掲載します。
※ Githubのリポジトリ等は伏せ字にしてあります。
package mysql import ( "context" "time" "github.com/xxxxxxxx/xxxxxxxx/infrastructure/adaptor" "github.com/pkg/errors" ) type ComicMySQL interface { Get(ctx context.Context, id int32) (*ComicRecord, error) } type comicMySQL struct { db adaptor.DBAdaptor } var _ ComicMySQL = &comicMySQL{} type ComicRecord struct { ID int32 `db:"id"` Title string `db:"title"` Author string `db:"author"` Series int32 `db:"series"` Publisher string `db:"publisher"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } func NewComicMySQL(db adaptor.DBAdaptor) ComicMySQL { return &comicMySQL{ db: db, } } func (m *comicMySQL) Get(ctx context.Context, id int32) (*ComicRecord, error) { query := "SELECT id, title, author, series, publisher, created_at, updated_at FROM comics WHERE id = ?" var rec ComicRecord err := m.db.Get(ctx, &rec, query, id) if err != nil { return nil, errors.WithStack(err) } return &rec, nil }
package repository import ( "context" "github.com/xxxxxxxx/xxxxxxxx/domain/entity" "github.com/xxxxxxxx/xxxxxxxx/domain/factory" "github.com/xxxxxxxx/xxxxxxxx/infrastructure/persistent/mysql" ) type ComicRepository interface { Get(context.Context, int32) (*entity.Comic, error) } type comicRepository struct { m mysql.ComicMySQL } var _ ComicRepository = &comicRepository{} func NewComicRepository(m mysql.ComicMySQL) ComicRepository { return &comicRepository{ m: m, } } func (r *comicRepository) Get(ctx context.Context, id int32) (*entity.Comic, error) { rec, err := r.m.Get(ctx, id) if err != nil { return nil, err } return factory.ToComic(rec), nil }
package factory import ( "github.com/xxxxxxxx/xxxxxxxx/domain/entity" "github.com/xxxxxxxx/xxxxxxxx/infrastructure/persistent/mysql" ) func ToComic(in *mysql.ComicRecord) *entity.Comic { return &entity.Comic{ ID: in.ID, Title: in.Title, Author: in.Author, Series: in.Series, Publisher: in.Publisher, } }
普段のコーディングでは無口になってしまいがちですが、ライブですのでそうもできません。自分がいま何をしようとしているかをつねに話しながらというのは慣れないのでなかなかたいへんでした。観覧のチームメンバーのツッコミがありがたかったです。
ネタとして仕込んだCopilotはなかなかウケがよかったです。少しコードを書くと続きのコードをAIがいいかんじに提案してくれるのはわりと衝撃的です。微妙に使えないコードが混じるのもネタとしてよかったと思います。
やってみた気付き
なかなか人前で話す機会が無いので、正直なところやる前はかなり憂鬱でした。 しかし発表する前提で物事を考えると、これはなんでこうなってるんだっけというのを説明出来ないといけないので、あらためて調べたり学び直すいいきっかけになった様に思います。
またやれと言われるとプレッシャーがありますが、たまにこういう機会があるといい学び直しの機会になる様に思います。
あと余談になりますが、AIを使ったコーディングはかなり便利だと感じました。以前から関数名などのサジェストなどはエディタの機能としてありましたが、それがさらに進化した様な便利さでした。コードを書くスピードが格段にあがった様に思います。プロジェクト内でのコードの書き方に似せた出力がAIから行われるのもよかったです。AIはまだまだ微妙な提案をしてきたりもしますが、この進化はとまらないのでしょうね。
ヤプリでは一緒にプロダクトの成長を加速させてくれるフロントエンドエンジニアを積極募集しています。