週刊SleepNel新聞

SleepNel所属のぽうひろが日々の個人開発で気になったことを綴ります。

PDF生成ライブラリwkhtmltopdf(Python)の代替を探す旅 〜Playwright (Kotlin) 編 〜

この記事は、クロスマート・テックアドカレ14日目の記事です。
qiita.com

前日は、@gomagoma000さんの「ディズニーランドを最も効率よく楽しむ方法をグラフ理論Pythonで考える」という記事でした。
ディズニーランド好きなガチ勢のみなさんにはとても有用な記事です。
ぜひ見て見てください!
qiita.com


さて、今回の記事では
PDF生成ライブラリwkhtmltopdf(Python)の代替を探す旅 〜Playwright (Katlin) 編 〜
と題しまして、書いていこうと思います。

みなさん、Pythonのhtmlからpdfを作成するライブラリ wkhtmltopdf はご存知でしょうか。
Pythonではかなり広く使われていたライブラリなのですが、内部で利用しているQt WebKit(Qt のレンダリングエンジン)が公式サポート停止してしまいました。
その流れを受け、wkhtmltopdf自体もメンテが停止してしまいました。GitHubページも2023年1月を以ってアーカイブ状態にあります。
github.com

一応、ライブラリ自体はまだダウンロードできる状態にあるため、使うことはできますが、もう機能追加もバグがあっても修正されない状態にあります。
ダウンロードもいつできなくなってもおかしくない状態です。

Pythonで使える良い代替ライブラリがないか探して使って見たりしたのですが、うまくcssなどが当たらない、性能がwkhtmltopdfより出ない、などの理由により、これというものが見つかっていませんでした。

ですが、ふと思いつきました。

別にPythonにこだわらずとも別の言語で使えるものがあるのでは・・・?

唐突に思いついた私

最近、KotlinでMicronautを使い始めていた私。 Kotlin(Java)で使えそうなライブラリを探しました。

flying-saucer、itextなども使って見ましたが、うまくcssなどが当たらない、そもそも有料 など、決定打に欠けましたが、

良いものがありました! Playwright (Java版) です!!
playwright.dev

そもそもPlaywrightとはWebアプリのE2Eテスト向けのライブラリなのですが、ブラウザ操作の自動化などに使われることも多いです。
さらに、WebページをPDF化する機能もあり、PDF生成ライブラリとしても使われています。
内部でブラウザを動作させてWebページを表示するので、最新のcss3やhtml5の再現も全く問題ありません。

こちらはPython版もあり、代替として検討もしたのですが、性能があまり出ませんでした。

もしかしたらJavaなら十分なパフォーマンスが出るかもしれないということで、今回試してみました。

性能比較

それでは早速実験です。今回はhtml文字列をPDF化する という箇所に限定して計測します。(データベースからhtmlを生成する、などの別の箇所のパフォーマンスについては計測対象にしない)

htmlは、PDFにすると3ページほどの小さいページと、100ページを超える大きいページを利用しました。

Pythonのwkhtmltopdfはこのようなコードで計測しました。

import tempfile
from datetime import datetime
from jinja2 import Template
from starlette.templating import Jinja2Templates

def get_public_pdf():
    templates = Jinja2Templates(directory=str(Path(BASE_DIR, "app/templates/")))
    template: Template = templates.get_template("sample.html")
    html = template.render()
    # テンポラリファイルにPDFを作成する
    file_path: str = tempfile.NamedTemporaryFile(mode="w").name
    print("開始日時" + (datetime.now()).strftime("%Y-%m-%d %H:%M:%S.%f"))
    Pdf.create_by_html(html, file_path)
    print("終了日時" + (datetime.now()).strftime("%Y-%m-%d %H:%M:%S.%f"))

Playwright(Kotlin) はこのようなコードで計測しました。
ブラウザは毎回新しく開かないように、Factoryで一度開いたら使い回す工夫をしています。

import com.microsoft.playwright.Browser
import com.microsoft.playwright.Page.PdfOptions
import com.microsoft.playwright.options.Margin
import io.micronaut.http.annotation.*
import java.nio.file.Paths
import java.time.LocalDateTime

@Controller("/public/pdf")
class PdfController(private val browser: Browser) {
    @Get("/")
    fun createPdf() : String {
        // resourcesのsample.htmlを読み込む
        val html: String = PdfController::class.java.getResource("/sample.html").readText()
        println("開始日時: " + LocalDateTime.now())
        val context = browser.newContext()
        val page1 = context.newPage()
        page1.setContent(html) // HTMLをセット
        // PDFとして保存
        page1.pdf(
            PdfOptions()
                .setPath(Paths.get("example.pdf")) // 保存先のパス
                .setFormat("A4") // 用紙サイズ(例: A4, Letter など)
                .setPrintBackground(true) // 背景の印刷を有効化
                .setMargin(Margin().setTop("6mm").setRight("6mm").setBottom("6mm").setLeft("6mm")) // 余白
                .setScale(0.8) // スケール
        )
        page1.close()
        println("PDFが生成されました: example.pdf")
        println("終了日時: " + LocalDateTime.now())
        context.close()
        return "OK"
    }

}


PDFはこのような明細レコードがたくさんある表のような請求書PDFになっています。

実験結果

それぞれ、何回か計測した結果をグラフでまとめてみました。

Python wkhtmltopdf は小さいPDFで概ね2秒、100ページ越えのPDFでは概ね7〜8秒
Java(kotlin) playwrightは 小さいPDFで概ね0.2〜0.3秒、100ページ越えのPDFでは概ね2〜3秒
という結果となりました。

Java(kotlin) playwrightがサイズに関わらず、安定して早くPDF生成できるということがわかりました!

Javaはやっぱええなぁ・・・と思うと同時に、PDF生成処理を主サービスからは切り離して、共通サービスとして利用することで、
性能もメンテナンスが活発にされているライブラリに乗り換えることができそうだ、という道筋を立てることができました!!

まとめ

今回の記事では、PythonのPDF生成ライブラリ wkhtmltopdf のサポート終了を受け、Kotlinで利用可能な代替ライブラリ Playwright を活用したPDF生成の実験結果をご紹介しました。

結果として、Playwright(Java版)は性能面で非常に優れており、特に大規模なPDF生成においても安定した高速処理が可能であることが分かりました。最新のCSS3やHTML5の再現性が高い点も、大きな魅力です。

また、KotlinやJavaを活用することで、PDF生成処理をサービスから切り離し、共通サービスとして再利用するアーキテクチャの可能性が広がりました。これにより、性能向上だけでなく、メンテナンス性や将来的な拡張性も期待できます。

既存の環境にこだわらず、他の言語や技術に目を向けることで新しい選択肢を発見できるということを、今回の試行で改めて実感しました。

PDF生成の課題でお悩みの方は、ぜひ一度 Playwright を試してみてはいかがでしょうか。特にKotlinやJava環境での利用を考えている方には、非常におすすめできるソリューションです!

最後まで見ていただき、ありがとうございました!

入退室メール通知WebアプリがGoogle AppSheetで1時間で完成しちゃった話

みなさん、こんにちは。ぽうひろです。

この記事は、クロスマート・テックアドカレ9日目の記事です。
qiita.com

前日は、たるたるさんの「NewRelic初心者が資格試験を通じて学んだこと」という記事でした。
資格まで取ってしまうという前のめりさがサイコーな記事です。
現場にNewRelicを最初に導入したの私なので、詳しい人が増えて感慨深いです。
まだ読んでない方は、ぜひ読んでみてください!
qiita.com


今回はちょっとクロスマートの業務を離れまして、
「入退室メール通知WebアプリがGoogleAppSheetで1時間で完成しちゃった話」という記事を書きたいと思います。

私はクロスマートに入社する前、フリーランスで色々なことをやっていたのですが、その中の一つとして
子ども向け(小学生〜中学生くらい)のロボット・プログラミング教室をやっていまして、今もまだ続けています。

フランチャイズのため、教材は毎月本部の方から届くとはいえ、場所取りなども含め、そのほかは自分ですべてやる必要があります。

その中で、お子さんだけで通わせている親御さんから、「子どもが確実に教室に行ってるかどうかが分かりにくい」という要望が上がってきました。

お子さんは開始時刻は一緒なのですが、それぞれのタイミングで教室に来るため、その度に親御さんに連絡するわけにはいきません。
土曜日は連続で授業コマがあることもあり、入れ替わりのタイミングもなかなか忙しいです。

なんとか、これを解決する方法はないか、と考えました。

一番ラクなのは、世の中にある有料サービスを利用することでした。

nyutai.bpsinc.jp

こんな良さそうなサービスを見つけました。基本料金は3300円 で60人から人数*55円
とてもリーズナブルに思えます。

ですが個人で運営している教室。収益状況も火の車です。
せっかくエンジニアをやっていることですし、こんなに立派なシステムじゃなくても良いので「自分で作れないかな?」と考えました。
インフラ代で3300円を超えては本末転倒です。なるべく無料で、かつ、自分が使えるだけで良い、という条件でやり方を考えてみました。

仕様は以下のように考えました。

  1. スマホ or ChromeBook or Mac, Windows いずれかで動作すること
  2. 数日で開発ができること
  3. 在籍している生徒を一覧で表示できる。生徒それぞれには入退出を連絡する親御さんのメールアドレスを登録できる
  4. 入室時 or 退出時に 生徒一覧の該当生徒の入退出ボタンをおすと、登録されている親御さんのメールアドレスに時間付きでメールされる。

うちにはプログラミング教室で使っていたChromeBookがあったため、最初はGoogleSpreadSheetにマクロ作って生徒シートにボタンをつけるという方針を考えていました。
その辺りを調べていたのですが、たまたまこのようなページに辿り着きました。

cloud.google.com

Google AppSheet です。
見ると、ノーコードでアプリケーション開発と自動化ができるのがウリみたいです。Googleがノーコードの開発環境を出しているのを今回初めて知りました。

とりあえず、個人で使う分には無料というのと、調べるとメールを送信する機能も使えるようです。
今回はこちらを使って、仕様に沿ったアプリが作れないかチャレンジしてみることにしました。(うまくいけば、毎月3,300円の節約ができる!!)

何はともあれまずはGet Started

about.appsheet.com

こちらのAppSheetのホームっぽいページの右上にある「Get started」ボタンを押しましょう。

ログインの方法を聞かれますので、今回はGoogleを選びます。

権限はとりあえず全部選んでおきましょう。データ保存に使う気がします。

いつの間にかアプリ作りフェーズ

すると、こんな画面が出てきます。おや、Start with Gemini なんていうボタンが虹色に光ってますね・・・w
「せっかくなので俺はこの虹色のボタンを押すぜ」

Start with Gemini...?


すると、アプリのアイデアやプロセスを書く欄が出てきます。 もしかして、勝手に作ってくれるのか、、、?

試しに、「教室の生徒管理アプリ。生徒には親のメールアドレスを登録でき、生徒の入室/退出ボタンを押すと、親にメール通知される。」
と入力してみます。(日本語を打つと、途中でアプリを作り始めてしまうので、日本語入力する際はメモ帳などに書いてから貼り付けると良いです。)

しばらくすると、何やらデータベースのテーブルリストのようなものが提案されてきました。
Attendance Recordsが入退出レコードでしょうか。TimestampとStatusがあるのでそれっぽいですね。
Studentesは生徒、Parentsは親ですね。 Teachersは先生で、Classroomsは教室でしょうか。後ろの2つは要らなそうですね。

選ぶとテーブルの詳細が表示されます。削除もできるようなので、TeachersとClassroomsは消してしまいましょう。

削除してスッキリしました。これでCreate Appしてみましょう。

待つこと、20秒ほど(あんまり待ってない)

は? 完成した。。。?

Try it outとか書いてあるんだが?

右にスマホ画面とかタブレットとかPC画面とかに切り替えるボタンがあります。
それぞれの画面サイズに合わせて、Attendance Recordsのリストが表示されていますね。


「まさか、こいつ動くのか。。。?」


動いたあああああああああ!!!

生徒に親のメールアドレスの紐付けられています。変更して保存もできます。

データは、左メニューのDataボタンを押して、テーブルを選んでから「View data source」ボタンで別タブで見れます。

dataからテーブルを選んで、View data source

データが見れます。今回、ENTERデータを追加したのですが、ちゃんと24/12/8 で登録されています。

すごいですね・・・!! データベースとWebアプリ画面が連動して動いていますので、このままでも使えちゃいそうです。

Automationを使ってメール送信

ですが、一個だけ問題があります。 入室データを登録するとメールが送られるわけではなく、メールクライアントが開く状態です。
ここは、自動でメールが送られて欲しいですよね。自作のアクションを作りには「Automation」から作成します。


この画面からCreate my first automation ボタンを押してみましょう。


また何か聞かれていますね。 Attendance Recordsにデータが作られたらメールを送信したいので
「When a new Attendance Records record is created, 」と途中まで入力すると、サジェストが出ました。これを選びましょう。

すると、なんだかビジュアルプログラミングっぽいフロー図が出てきました。

このSend a notificationを押して、詳細を設定していきます。
メールを送りたいので、Send an Email を選びます。

宛先は、Toの部分で設定しますが、これをStudentの親のメールアドレスにしたいですね。

Addを押して、フィルタボタンを押し、入力フィールドをクリックします。


送りたい、ParentsのEmailをInsertします。

あとは、メールタイトルや本文をカスタマイズします。

メールのFromもいじれます。ただし、無料版ではメールアドレスFromまでは指定できません。必ずFromはnoreply@appsheet.comからになりますのでご注意ください。

ここまでできたら、実際にレコードを登録してみましょう。

メールが届きました!!  やったね!!

まとめ

いかがだったでしょうか。
あっという間にGoogle AppSheetで便利なWebアプリが作れてしまいました。
Google Workspaceと連携していると、もっといろいろなことができるみたいなので、会社の業務システムなどで便利なツールを簡単につくれるかもしれませんね!

今回は個人向けアプリのため、利用者数もデータ量も少なくFreeプランでも良いですが、もう少し大掛かりになってくると有料プランにした方が良さそうです。

簡単に試せますので、ぜひ試してみてください!

では、次のアドベントカレンダーの記事をお楽しみに!!

AWS S3 Presigned URLについてまとめてみる(Python, Kotlinの実装例つき)

みなさん、こんにちは。ぽうひろです。

この記事は、クロスマート・テックアドカレ5日目の記事です。
qiita.com

前日は、私の【エンジニアポエム】目指すテックリード像には近づけたのか?
という記事でした。まだみてない方はチェックしてみてください。一応、会社でちゃんと振り返ってえらいと褒められました。
sleepnel.hatenablog.com

今回は技術の話をします。みなさんご存知、AWS S3ですが、私も一応仕事で使った経験はありました。
が、この会社にきて初めて凄腕SREの方にPresigned URL というものを教えていただき、サービスに導入しました。
めちゃめちゃいい仕組みです。
もしかしたらまだ知らないエンジニアの方もいらっしゃるかもしれないので、今回こちらについて紹介したいと思います。


S3 Presigned URLとは

Presigned URLは日本語で言うと「事前署名付きURL」と言う翻訳になります。
S3にファイルをアップロードしたりダウンロードしたりするには、権限(許可みたいなもの)が必要になるのですが、それが付与され、かつこの場所に置いてね〜とS3から置き場所の事前予約を取ったURLというイメージになります。
システムでよく使う方法としては、画面のアップロード画面からファイルをバイナリとしてバックエンドAPIへPOSTし、バックエンドAPI内でその受け取ったバイナリをS3へアップロード(S3へのアクセスするための権限は設定されている)する といった構図をよく目にすると思います。

従来の方法を図にするとこんな感じですね

ユーザがアップロードしたファイルは最終的にサーバサイドがS3にアップロードしている

Presigned URLを使ってアップロードする方法だと、下図のように、一旦フロントからサーバサイドにPresignedURLの発行を依頼します。
(今回はサーバサイド経由でS3にPresigned URL発行を依頼していますが、フロント側でもフロント用のS3 SDKと認証ファイルを保持していればS3にPresigned URL発行を依頼することもできます。)
サーバサイドはS3からPresignedURLを発行しフロントに返します。フロントはそのURLに対してファイルアップロードするという手順になります。
URLというくらいなので、S3のファイルアップロードを受け付ける専用のURLになっています。(ダウンロードについてもPresigned URLが発行可能です。)

弊社ではS3ダイレクトアップロードと呼んでいます

Presigned URLを使わない場合となにが違うの? と言いますと、実際にS3にアップロードするのが「サーバーサイド」になるのか「フロント(Web画面か)」の違いになります。
じゃあユーザにとってはそんなに変わらないよね?と思うかもしれませんが、ユーザだけでなくシステム提供側にもメリットがある方式です。ここからは、Presigned URLのメリットを紹介していきます。

Presigned URLを使うメリット

処理をシンプル化

Presigned URLを利用すれば、フロント(ブラウザやアプリ)から直接S3にデータをアップロードまたはダウンロードでき、バックエンドサーバーを経由する必要がなくなります。S3とのやり取りはクライアント側で完結できるため、システム全体の処理がシンプルになります。

バックエンドサーバーリソース消費削減

バックエンドを経由せずにデータを直接S3とやり取りできるため、バックエンドサーバーリソースの消費が抑えられます。

ネットワークトラフィック量の軽減

従来の方法だと、バックエンドサーバーがクライアントからのリクエストを受け取り、S3バケットとの間でデータ転送を行う場合、サーバーのネットワーク帯域幅を多く消費します。Presigned URLを利用すれば、クライアントが直接S3にデータをアップロード/ダウンロードするため、バックエンド側にトラフィックが発生しません。

スケールしやすいアーキテクチャ:

大量のファイルを扱うアプリケーション(動画ストリーミング、画像アップロード/ダウンロードなど)では、サーバーが中継を行うとトラフィック増加に伴いリソースのスケールが必要になります。Presigned URLを使えば、S3自体がスケールするため、インフラの運用コストが削減されます。

柔軟なセキュリティ制御

Presigned URLは特定の操作(GET、PUTなど)に限定的なアクセス権を付与できます。そのため、IAMポリシーやバケットポリシーで広範囲にアクセスを許可する必要がありません。
また、Presigned URLには有効期限が設定できるので、期間限定一時的な共有リンクを作るみたいなこともでき、柔軟なセキュリティ制御を行うこともできます。

高速なデータ転送

エンドユーザーのクライアントはPresigned URLを使用して、直接S3バケットと通信します。この通信はAWSの最適化されたグローバルインフラを活用して行われるため、データの転送速度が速いです。(アクセルモードというpresignedURLも作ることができます!)
AWS内での通信は非常に高速かつ安定しているため、エンドユーザーもその恩恵を受けられます。また、地理的に分散したエッジロケーションから効率的にデータを配信可能です。

実装の仕方

たくさんメリットがあることを感じていただいたと思いますので、具体的な実装方法を軽く紹介したいと思います。

Pythonの場合

PythonでS3を扱う場合、AWSから出ている便利なSDK boto3というライブラリを利用します。

1. boto3がインストールされていない場合は以下でインストールします:

pip install boto3

2. AWS認証情報の設定:

環境変数、~/.aws/credentialsファイル、またはIAMロールを使用してAWS認証情報を設定します。
必要な権限:
s3:PutObject(アップロード用)
s3:GetObject(ダウンロード用)

3. boto3でPresigned URLを生成

import boto3
from botocore.exceptions import NoCredentialsError

# S3クライアントを作成
s3_client = boto3.client('s3')

def generate_presigned_url(bucket_name, object_key, method, expiration=3600):
    """
    S3のPresigned URLを生成する関数
    :param bucket_name: S3バケット名
    :param object_key: オブジェクトのキー(ファイルパス)
    :param method: 操作('get_object'または'put_object')
    :param expiration: 有効期限(秒)
    :return: Presigned URL(文字列)
    """
    try:
        presigned_url = s3_client.generate_presigned_url(
            ClientMethod=method,
            Params={'Bucket': bucket_name, 'Key': object_key},
            ExpiresIn=expiration
        )
        return presigned_url
    except NoCredentialsError:
        print("AWS認証情報が見つかりません。")
        return None

# 使用例
bucket_name = "your-bucket-name"
object_key = "your-file-path/your-file.txt"

# アップロード用URLを生成
upload_url = generate_presigned_url(bucket_name, object_key, 'put_object')
print(f"Upload URL: {upload_url}")

# ダウンロード用URLを生成
download_url = generate_presigned_url(bucket_name, object_key, 'get_object')
print(f"Download URL: {download_url}")

Kotlin(Java)の場合

1. Gradleに依存関係を追加 プロジェクトのbuild.gradle.ktsに以下を追加します。

implementation("software.amazon.awssdk:s3:2.20.0")

※バージョンは最新を確認してください。

2. AWS認証情報の設定
Pythonの時と同じです。
環境変数、~/.aws/credentialsファイル、またはIAMロールを使用します。

3. Presigned URLを生成
以下のコードで、Presigned URLを生成します。

import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.presigner.S3Presigner
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest
import software.amazon.awssdk.services.s3.model.GetObjectRequest
import software.amazon.awssdk.services.s3.model.PutObjectRequest
import java.time.Duration

fun generatePresignedUrl(bucketName: String, objectKey: String, method: String, expirationMinutes: Long): String {
    // S3Presignerを作成
    val presigner = S3Presigner.builder()
        .region(Region.US_EAST_1) // リージョンを指定
        .credentialsProvider(ProfileCredentialsProvider.create()) // 認証情報プロバイダー
        .build()

    return when (method.lowercase()) {
        "get" -> {
            // ダウンロード用Presigned URLを生成
            val getRequest = GetObjectRequest.builder()
                .bucket(bucketName)
                .key(objectKey)
                .build()

            val presignRequest = GetObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(expirationMinutes))
                .getObjectRequest(getRequest)
                .build()

            presigner.presignGetObject(presignRequest).url().toString()
        }
        "put" -> {
            // アップロード用Presigned URLを生成
            val putRequest = PutObjectRequest.builder()
                .bucket(bucketName)
                .key(objectKey)
                .build()

            val presignRequest = PutObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(expirationMinutes))
                .putObjectRequest(putRequest)
                .build()

            presigner.presignPutObject(presignRequest).url().toString()
        }
        else -> throw IllegalArgumentException("Invalid method: $method")
    }
}

fun main() {
    val bucketName = "your-bucket-name"
    val objectKey = "your-folder-path/your-file.txt"

    // アップロード用Presigned URLを生成
    val uploadUrl = generatePresignedUrl(bucketName, objectKey, "put", 60)
    println("Upload URL: $uploadUrl")

    // ダウンロード用Presigned URLを生成
    val downloadUrl = generatePresignedUrl(bucketName, objectKey, "get", 60)
    println("Download URL: $downloadUrl")
}

まとめ

S3を使ったシステムを構築することはよくあることだと思いますが、Presigned URLを使うことで、さらにシンプルで効率的な構成が可能になります。
セキュリティを確保しつつ、直接S3とやり取りできるため、大容量データもスムーズに扱え、エンドユーザーにも開発側にも嬉しい仕組みです。
システムの負担を軽減しコスト削減にもつながるので、特に多くのデータやユーザーを扱うサービスには非常に有益です。
まだご存知なかった方は、ぜひこの機会に導入を検討してみてください。きっとシステム全体のクオリティを向上させるはずです。

最後までお読みいただき、ありがとうございました!!

次のアドベントカレンダーの記事は、UIデザイナーおしまさんの「PM向けにfigmaの「オートレイアウト」と仲良くなるための勉強会をした話」という記事です。

見逃すな〜!!

qiita.com

【エンジニアポエム】目指すテックリード像には近づけたのか?

みなさん、こんにちは。ぽうひろです。

この記事は、クロスマート・テックアドカレ4日目の記事です。
qiita.com

前日は、私の【エンジニアポエム】追い求めていたリモートワークはどうだったのか
という記事でした。まだみてない方はチェックしてみてください。
sleepnel.hatenablog.com

さて今回もポエムっぽくなってしまうのですが、「目指すテックリード像には近づけたのか」というタイトルで書かせていただきます。

目指せイケてるテックリード

会社のエンジニアチームの規模が大きくなり、チームが2つに分かれてしばらくしました。バックエンド・フロントエンドで一人ずつで開発していたところに、新規メンバーが増え、なんとなくチームリーダーっぽい動きをして3〜6ヶ月たった頃だと思います。

採用面談に出る機会も増え、私は考えます。どうやら、他の開発組織では「テックリード」という肩書きがあるらしいぞ。うちにはEM(エンジニアマネージャ)はいるけどテックリードはおらんな・・・開発リーダーと何が違うんだろ。。。

テックリードをぼんやり考え始めた1年前

いきなりCTO?とかもなんか違うような気がしてて(そんな器でもないし)、じゃあ、テックリードかな! 若い人たちロールモデルになれたら役に立てそうかな〜と、

今年1年私は会社で特に設置もされていなかった肩書き「テックリード」を目指します、と宣言して頑張っておりました。
いや、頑張ってたというかいつも通りというか。けど、フリーランス経験長くて一人で動くことが多かったので、頑張って全体を見るように意識はしていたと思います。

最初に私なりに考えた、目指すテックリード像がこちらです。(原文ママ

思い描く開発チームリーダー像

  • プロダクトの問題(エラー、バグ、性能、問い合わせ)を放置しない。メンバーにも頼りつつ、責任持って解決する。
  • タスクを適切にメンバーに割り振る
  • タスクを遂行してくれたメンバーに感謝を示す(内に秘めるのは意味ない)圧倒的感謝。
  • メンバーが快適に開発できるようにする(そのために必要なことはやる)
  • 新しい技術、便利な技術のアンテナを張り、率先して取り入れる
  • プロダクトの基盤(フレームワーク、ライブラリ、など)を新鮮に保つ(適切なバージョンアップ)
  • 各メンバーのヘルスチェックをする(健康面、メンタル面などに気を配る。難しい!)
  • プロダクトのヘルスチェックをする(性能や機能で怪しい部分を予見する。難しい!)
  • チーム内で質問・相談をしやすい雰囲気を作る(いつでも相談に乗れる気持ちの余裕を持つ。難しい!)
  • 自分タスクを抱えすぎずメンバーを頼る(一番大事かもしれん)
  • メンバーからの提案や提言に耳を傾け、公平・冷静に判断する。
  • ルーズボールを拾って、対処する(タスクをふる、自分でやる等)

ちゃんと目に見えるように、Slackにも貼りました。

この時は開発リーダーって言ってる


で、一年経ちましたので、自分どれくらいできてるかな??というのを各項目、10点満点で採点していきたいと思います!!!対戦お願いします!

思い描く開発チームリーダー像 採点

プロダクトの問題(障害対応、問い合わせ対応、性能改善)を放置しない。メンバーにも頼りつつ、責任持って解決する。

7点 - 障害対応・問い合わせ対応についてはスピード感持ってメンバー全員が取り組む姿勢がありました。性能改善についてはNewRelicで全員状態を見れるようになっているが、目標を定めて自ら動けるメンバーはまだいないかな〜という感じです。来年は能動的に動けるメンバーが増えるように啓蒙していきたいと思います。

タスクを適切にメンバーに割り振る

8点 - 基本的に日々の機能追加や改修はメンバーに担当してもらうように意識的に動けている。緊急の対応であったり、チーム内で拾えるメンバーがいないこぼれ球を拾う動きができた気がする。適切に割り振れているか、は受け取った側の意見もありそうなので、堅めに8点。

タスクを遂行してくれたメンバーに感謝を示す(内に秘めるのは意味ない)圧倒的感謝。

9点 - リリースありがとうだったり、良い動きをしているメンバーを褒めるというのはチームを超えて積極的にできた気がする。会社には週3回ある全体朝会で「ほめぽコーナー」という、感謝があったらみんなの前でそれを伝えるという文化があるのだが、おそらくエンジニアの中では一番誉める回数が多いと思う。(多分。PMには負けちゃうかもしれないけど) 今後も全体を見て、めっちゃ褒めていきたい。

メンバーが快適に開発できるようにする(そのために必要なことはやる)

7点 - 別のチームのバックエンドにswaggerを導入したり、マイグレーションをskipしてビルド時間を短くするアイデアなど、自チームで良かった取り組みを積極的に他チームへ展開する行動を取ることができた。あと1つやり残しがあるので、ちょっと減点。

新しい技術、便利な技術のアンテナを張り、率先して取り入れる

8点 - Slackに技術共有チャンネルを作って、twitterの記事などを共有したりしている。PolarsやPlaywright、Kotlin、Micronautの導入など、率先して新しい技術を試して、サービスをよくできる見込みがあるものは導入していっている。AI周りはちょっと詳しくないので少し減点。(別の人に任せている)

プロダクトの基盤(フレームワーク、ライブラリ、など)を新鮮に保つ(適切なバージョンアップ)

10点 - dependabotによる自動チェックを毎月取り入れることができている。自分以外にもやってくれるメンバーが増えた! こまめにアップデートしていくことのメリットをめちゃめちゃ感じている。(後回しにして、問題が起きてからアップデートすると大体困難にぶち当たる) 満点を出せる対応ができていると思う。

各メンバーのヘルスチェックをする(健康面、メンタル面などに気を配る。難しい!)

5点 - 毎日あるチーム朝会(デイリースクラム)で、雑談を挟みつつ顔色も見つつ、状態を把握しようとしている。問題の予防になっているのかどうかわからない、手探り状態。ちゃんとしたメソッドがあるのかもしれないが、知識が足りていない気がする。

プロダクトのヘルスチェックをする(性能や機能で怪しい部分を予見する。難しい!)

5点 - 定期的にヘルスチェックするというより、ユーザやCSからの問い合わせで悪い部分に気づける、ということの方が多いのが反省ポイント。NewRelicやSentryなどのツール類はあるので、定期的なフローにしてシステムで解決していきたい問題。新しく入ったSREメンバーが頼りになるので、ServiceLevelなどを協力して策定、改善啓蒙するなど、やっていきたいポイント。

チーム内で質問・相談をしやすい雰囲気を作る(いつでも相談に乗れる気持ちの余裕を持つ。難しい!)

6点 - ある程度相談はもらえている感じはするが、どうしても忙しいタイミングがあって、話しかけんなオーラを出してしまっているかもしれない。もっと余裕のある大人になりたい(そこ?)

自分タスクを抱えすぎずメンバーを頼る(一番大事かもしれん)

6点 - 自チームのサービスについてはかなり頼ることができているが、2つほど自分しか見ていないプロダクトを抱えてしまっており、そこがマイナスポイント。それも本当は誰かに頼りたいが、もう人がいない・・・・

メンバーからの提案や提言に耳を傾け、公平・冷静に判断する。

7点 - 提案や提言があれば聞いて、アドバイスをするようにしている。が、「こうやった方がいい」などと早めに言ってしまうことも結構あり、ちょっと我慢が足りないかもしれない。成長を促すために、もう少し「待つ」というのをしても良いのかもしれない。

ルーズボールを拾って、対処する(タスクをふる、自分でやる等)

9点 - 1年を通して意識してボールを拾うことができたと思う。まだやれるやろ!という思いも込めて9点。

合計 87/120 という結果になりました。5段階評価で4という感じでしょうか。

まとめ

全体的によくできてそうな感じはするのですが、対メンバー(様子を見るとか、成長を促すような相談の受け方など)の対応がまだいい結果として出てないなと感じます。
また、対プロダクトにおいては、サービスレベルの向上であったり、性能劣化を予防するシステマチックな改善フローの構築が甘いと感じます。

あと、自分が頑張れば良いものはやればいいだけなのでやるのですが、やっぱり育成面。メンバーが自発的にこういったことに取り組むようになる、そうしたくなるように仕向けるというあたりは、やっぱり難しいなと感じています。

来年まだテックリードをやっているかはわからない(状況は数日で変わったりする。ベンチャーあるある)のですが、今回振り返って足りないなと感じたことは、取り組んでいけるように自分を向上させていきたいなと思うのでした。


いつものノリと違って真面目すぎる感じもしますが、ちょうど良い機会に振り返ることができて良かったなと思います。
(どうせ会社の目標振り返りでやらなきゃいけなかったし、という下心もありつつ。)
また来年、成長した姿を報告できるように精進したいと思います。


最後までお読みいただきありがとうございました!!

この記事が、テックリードを目指している・目指そうかな?と考えている方々に少しでも参考になれば幸いです。

明日はもう少し技術よりの記事を書きたいと思います。 お楽しみに!!

【エンジニアポエム】追い求めていたリモートワークはどうだったのか

みなさん、こんにちは。ぽうひろです。

この記事は、クロスマート・テックアドカレ3日目の記事です。
qiita.com

前日は、ふぁるさんの【ライブラリ開発】リリースタグが切られた時に自動でnpmにpublishするGitHub Actions
という記事でした。まだみてない方はチェックしてみてください。
qiita.com


さて、今回は「追い求めていたリモートワークはどうだったのか」というポエム記事を書かせていただきます。

このブログを見ていただいていた方や、ツイッターランドで知り合いだった方は私がずっと「リモートワーク」を追い求めていたのはご存知なのではないでしょうか。それこそ10年くらい同じことを言っていたかもしれません。

詳しく知りたい方は、こちらの記事もぜひご覧ください。
sleepnel.hatenablog.com
sleepnel.hatenablog.com

リモートワークを強く意識したのは、家族(+子供)を持ったこととフリーランスになったことが大きかったです。
とにかく、通勤に使う時間と体力の浪費が我慢できなかった。
それを経て現在、あれだけ追い求めていたリモートワークができる環境に身を置いています。

3年前に入った弊社クロスマートは、エンジニアは基本的にフルリモート勤務となっています。
(ただし、関東近郊の本社に出勤できるメンバーは月曜日に出社する、というルールがあります。
私は出社できる位置に住んでいるため、フルリモートというわけではなくハイブリッドという形にはなります。)

ちょうどコロナがあったのと、弊社エンジニアは北海道から沖縄まで全国いろいろなところに住んでいるのもあり、
リモートワークは自然な形で浸透しています。

その辺を踏まえ、感想を言いますと、

控えめに言って過去イチ最高な環境です。

声を大にして言いたい。

控えめに言って過去イチ最高な環境です。(大事なことなので2回)


これだけだとわからないと思うので、良いところを詳しく説明していきます。

リモートワークの良いところ

  • 通勤時間の往復2時間(+HPが削られる)からの解放

これは言わずもがな。10時間労働が8時間労働になる。めちゃくちゃデカい。

  • 子供の行事(授業参観など)や家族のピンチ(通院、看病、学校からの呼び出し)に駆けつけられる。

子育て世代にはめちゃめちゃありがたい。

  • 家事に参加できる

食事の洗い物(朝・晩)、掃除機がけ、犬の散歩(朝)、ごみ出し(週3回)を担当しております。

  • 妻が仕事に出れる

在宅でできる仕事しかしていなかったのですが、最近、外に出て働くようになりました。 たまに子供が学校から早く帰ってくるような日でも私が昼食準備などができるので、安心して働きに出ています。

  • 気分を変えて好きな場所で働ける

ずっと同じ場所で開発の仕事をしているとマンネリ化してきます。 もちろん会社に行っても良いのですが交通費がかかってしまうので、近所のショッピングモールにあるカフェやミスド、フードコートに散歩がてら出かけて行って仕事ができます。家で黙々と一人でやるより、他の人がいるところでやる方が気が引き締まって、仕事も進みます。


とはいえ、ちょっと待ってさっき、ハイブリッド(月曜出勤)してるんでしょ? フルリモートじゃないじゃん、その部分は我慢せざるを得ないんでしょ?と思われるかもしれません。

が、嬉しい発見というか、この週イチ出勤も、絶妙なバランスで良いことがあることがわかってきました。

週1出社の良いところ

  • 同僚と辛いものを食べに行ける

私は辛いものが麻婆豆腐やスンドゥブチゲを筆頭に大好きです。ですが子供がまだそんなに辛いものが好きではないので、家族で外食に行ってもなかなか辛いものを食べよう!というお店は避けられてしまいます。
ですが、会社の同僚で辛いものが好きな人がいたので、集まって、「辛い部」を結成しました。
月曜ランチは基本的に辛い部でランチを食べに行こう!となっています。 辛い部が行ったお店、これからいきたいお店などもnotionに書き込んでいます。毎週出勤が楽しみです。

つらい部ではない
  • 困ったらすぐ横にいるので聞ける、最近どお?が言いやすい

同じチームで1つテーブルを拝借して、島を作ってみました。もう隣に仲間がいるので、困ったことがあったり相談があったらすぐ声かけて聞けます。デメリットは、沈黙が長すぎると私が話しかけちゃうことですねw 黙々と作業したい時はリモートワークの日が良い気がします。

  • 別部署の人が困ってることを聞けて、助けることができる

個人的に一番のメリットはここだと思います。リモートオンリーだと、MTGを行っていない別部署の困った事などをキャッチアップすることはどうしてもできません。出社するだけで、「ここ困ってるんです助けて下さい〜」という小さな声も拾うことができます。
うちの会社は、外食に携わるすべての人に喜ばれる商売をしよう。という「現場」を大切にするバリューを掲げており、自分も共感しています。
ですが、エンジニアにとっての現場はお客さん以外にも会社の中、すぐ隣の部署、営業さん、CSの人たち、チーム内の仲間、そんなところにもあると自分は考えて仕事をしています。

出社していないと聞けない悩みがあります


良いことばっかりで、デメリットないの?って思うかもしれませんが、マジで何もありません。
MTGしにくいよね?って思うかもしれませんが、弊社はGatherというオンラインオフィスツールを導入していますので、相談があればスムーズにビデオチャットが開始できます。勉強会や輪読会などのイベントもここで行っていますよ!!
リアルで開催するよりもハードルが低く、誰でもイベントが開催しやすいのが良いですね!!

強いていえば、運動不足になりやすいことでしょうか?

しかし、朝の犬の散歩は30〜45分くらい歩きますし、たまに環境を変えて外で仕事する際も散歩を兼ねています。体重はずっと60kgのまま、健康体です。


とまあ、いいことしか書けないエントリーでしたがいかがだったでしょうか。

もちろん、ベンチャー企業ならではという面もあるのではないかとも思いますが、ベンチャーはやはり採用が命です。

日本全国から気のあった優秀な仲間たちと一緒に(リモートではあるけれども)同じ空間・時間で働けるのは、人生の財産だなあと思います。

せっかくベンチャーやってるんですから、この恩恵を十二分に受けつつ、会社を良くしていきたいなあとリモートワークに挫折したあの時から7年を経て、改めて思うのでした。
(もし上場したりしてリモートワークが無くなったら、私のことだから、会社辞めちゃいそうですねw)

最後まで読んでいただきありがとうございました。


次回のアドカレは1日挟んで、もう少し技術よりの話をしたいと思います。

お楽しみに。

qiita.com

Kotlin Micronaut(マイクロノート)で作った経験を振り返ってまとめてみる

みなさん、こんにちは。ぽうひろです。

この記事は、クロスマート・テックアドカレ1日目の記事です。
これから12/25まで愉快な仲間たちの記事がたくさん出てきますので、どうぞお楽しみください!
qiita.com


さて、本記事では私の仕事で初参戦となりました、プログラム言語:Kotlin フレームワーク:Micronaut(マイクロノート)で作ったサービスが、ある程度形になって運用に乗り始めましたので、振り返りも兼ねてどうだったかをご紹介したいと思います。


選定理由

まず、それぞれの選定理由から。
プログラム言語:Kotlin についてですが、詳しい説明は ウィキペディアhttps://ja.wikipedia.org/wiki/Kotlinに譲りますが、
簡単にいうとJavaをもうちょっと便利に書きやすくしたプログラム言語 ということができます。
元々はAndroidアプリの開発向けに作られた言語だったのですが、Javaの主戦場のサーバーサイドプログラミングにも使えるね〜ってことで、最近はかなり流行ってきていると感じます。Kotlinを採用している企業がまとまっていたので紹介しますが、結構有名どころも使っているようですね!心強いです。
whatweuse.dev

メルカリ、タイミー、DeNA、マネフォなどなど名だたる企業ばかり

会社ではPythonがメイン言語となっていますが、私の長いエンジニア人生Javaとともに歩んできたと言っても過言ではありません。
大袈裟でなくJavaに飯食わしてもらってきた、と思ってるくらいありがたい存在ですし、好きな言語です。
それをより良くした言語ということですから、ずっと使いたいなあと思っていました。
今回のような仕事で使える機会なんてのは滅多にありませんので、希望を叶えてくれた現場には感謝しかありません。

続いて、フレームワークのMicronaut(マイクロノート)。
フレームワークを選定する際、私が意識していることは会社のテックブログにも書いたのですが、一番に意識しているのはやはり、「処理性能が良いこと」です。
いくら楽に作れるとかメリットがあっても、ユーザが遅い、と感じるようではやはり役に立ちません。

こちらは、各種フレームワークの性能をランキング形式で検索できるサイトです。
TechEmpower Framework Benchmarks
Springは380位くらいなのに対して、

Springは380位付近

Micronautは100位近辺とかなり性能が良いという評価なのがわかります。

micronaut 早い

PythonのFastAPIも300位くらいで健闘しているので、Pythonで作るなら個人的にはFastAPI一択だなあと感じています。

元々JavaにはSpring(Boot)というデファクトスタンダートとも呼べるWebフレームワークが存在しており、性能も先ほどのようにそこそこあります。
私も何回も使ったことがあり、これを使うのは無難な選択ではありました。ですが今回は「知ってることを使って安全に進める」よりも「新しい技術を実際に使いながら知識や経験を積み上げたい!との想いから、新しいフレームワークにチャレンジすることに決めました。

どんな感じで作るのか

Micronautは性能が良いですが、フレームワークとしてはSpringBootと似ています。(選定理由の一つではあります。)
今回もRestAPIの開発フレームワークとして利用しましたが、カバーしている領域もさほど変わりません。
フロントからのリクエストを受けつけるRouter層(Controller)、パス変数、BodyパラメータやレスポンスデータをDTO(Kotlin dataクラス) <-> JSONへ自動的に変換、DI(DependencyInjection)によるクラス間を疎結合にする仕組み、データベースのコネクション・トランザクション管理。エラーハンドリングでhttp status codeマッピング
Webフレームワークならこの辺は外せないよね、という仕組みは揃っています。

Controllerを一部抜粋

これは、プランIdに該当するプランを取得する GET /plans/{planId} のControllerですが、パス変数{planId}に当たる値がげgetPlan(planId:Int)の引数に自動セットされます。処理自体はPlanControllerのコンストラクタ引数private val planService: PlanService にDIされたサービスに委譲しています。
返り値は PlanResponseDtoで、これはフレームワークによって自動的にJSONに変換されて返ります。

パッケージ(ディレクトリ)構成

処理が多くなってくると、うまくクラスやレイヤーを分割するのが欠かせません。
今回のパッケージ構成はこのようにしました。 SpringBoot勢には馴染みがあると思います。

パッケージ構成

さして特別感はありません。一目見て、どこに何があるかが整理されていることが大事ですね。

3ヶ月使ってみてどうだったか

良いと感じたこと

作りやすいこと

先ほども言った通り「SpringBoot」とあまり作り方は変わりません。SpringBootはJavaデファクトスタンダードフレームワークであり、利用人口も多いです。なぜ人気があるかというと「作りやすい」「理解しやすい」という理由があると思います。
それとほぼ同じ感じで作れるということは大きなメリットになると考えました。

性能が良いこと

データ量や利用者がまだ少ないこともありますが、やはり性能は良いと感じます。
こちらはNewRelicで取ったレスポンス時間ですが、総じて100ms以下になっています。

Pythonで早い部類のFastAPIでもあまりこのような値はお目にかかりません(100msは概ね超えるイメージ)
MicronautというよりKotlin(Java)のコンパイラ言語の恩恵かもしれませんが、やはりこの速さは正義だなと自信を深めました。

公式ドキュメントが(思ったより)充実している

https://docs.micronaut.io/4.7.6/guide/
Micronautはこれまであまり聞いたことがなく、最近出たばかりのフレームワークでドキュメントもあまりないのではないか、と思っていたのですが、
上記のURLを見るとわかる通り、かなり充実したドキュメントがありました。
中でも、以下のようにJava Groovy Kotlin それぞれの書き方が列挙されているのはとても「気が利いている」と感じました。
それと同時にKotlinの人気の高まりも感じることができました。

Java Groovy Kotlin それぞれの書き方が列挙されている

良くないと感じたこと

ドキュメントがわかりにくい・探しにくい・参考記事が少ない

公式ドキュメントはあるのですが、基本は英語。あと何となく、記述内容もわかりにくい感じがしました。
今回、データベースライブラリとしてjOOQを採用し、一応MicronautもjOOQをサポートしているようなのですが、うまく動かないところやドキュメントが不十分に感じました。
あと多分、日本ではまだあまり使ってる人が少なそうで、検索してもなかなか日本語の記事がヒットしませんでした。
今回一番役に立ったのはChatGPTで、AIが提示してきた内容から公式ドキュメントを調べるというような逆引き調査で判明することが多かったように思います。
Micronautは性能恩恵がとてもある良いフレームワークだと思うので、個人的にもブログで記事を書くなど、盛り上がりに貢献できたらいいなと感じました。


以上、私のKotlin Micronaut(マイクロノート)で作った経験の振り返りでした。

もう少し日本ユーザが増えてきたら、とても大きなウェーブになりそうな予感を感じることができました。

来年はこの経験をもっと広く日本に広めることができたらいいなと思っています。乞うご期待ください!!!(いや、あまり期待せず・・・)

最後までお読みいただきありがとうございました!!

明日12/2 は @Fal-coffee さんがなんか書くらしいです!お楽しみに!
qiita.com

2024年も終わるので会社でアドベントカレンダー企画やってみる

みなさんお久しぶりです。ぽうひろです。

前に書いた記事はもう何年前でしょうか。

その時はまだフリーランスをしていた気がします。

あれから紆余曲折あり、私はとあるスタートアップ企業の会社員エンジニアをやっております。

たぶん3年くらい経ったと思います。

仲間も増え、とても楽しく仕事しております。

と、ここで、ずっとやりたかった企画

アドベントカレンダー」をやってみたく、会社Slackの自分のtimesチャンネルでゆるく募集をかけてみました。

f:id:pouhiroshi:20241130095611j:image

なんと1日で10人を超える参加表明をいただくことができました!

仲間がいるって素晴らしい!!

 

全ての日が埋まるほどではなく、かつ、全部埋まってると気持ちいいので、空いてるところはなるべく言い出しっぺの自分が書いていきたいと思います。

https://qiita.com/advent-calendar/2024/xmart

f:id:pouhiroshi:20241130095937j:image

(すみませんが、今回は内部メンバーのみの参加ということでお願いします🙇)

 

それではみなさん、ぜひお楽しみください!

開始は明日12/1 私

「Kotlin Micronaut(マイクロノート)で作ったサービスを振り返ってみる」からです!!