TECH PLAY

ニフティ株式会社

ニフティ株式会社 の技術ブログ

487

💡はじめに 🐘 Gradleの進化 🧩 課題解決 💻 Groovy DSL → Kotlin DSL → Version Catalog 💫 成果 💡はじめに こんにちは。ニフティ株式会社のLinです。 台湾出身のモバイルアプリエンジニアとして、社内で「マイ ニフティ」の Android および iOS 版の開発を担当しております。 今回は、Gradle の Groovy DSL → Kotlin DSL → Version Catalogへの移行をご紹介します。 🐘 Gradleの進化 Gradleは、Androidアプリの構築とビルドを管理するビルドシステムで、下記の機能があります: プロジェクトの構成管理 ライブラリの依存関係管理 リソースファイルの管理 プロダクションビルド、デバッグビルドの切り替え APKファイルのビルド マスコットはGradle君という名前の可愛い象です。 Android 開発における Gradle Script の記述方法には、Groovy DSL と Kotlin DSL の2つのパターンがあります。 Groovy DSL は従来からよく使われている記法で、シングルクォーテーションと括弧なしのダブルクォーテーションが特徴的です。 implementation 'androidx.room:room-rxjava2:$room_version' implementation "androidx.datastore:datastore-preferences:1.0.0" そして、2020年4月の Android Gradle Plugin 4.0.0 から、Kotlin DSL が導入されました。 括弧付きのダブルクォーテーションが特徴で、静的型付けによる型安全などのメリットがあります。 implementation("androidx.room:room-rxjava2:$room_version") しかし、Android の発展に伴い、Gradle の既存機能にはいくつかの課題がありました: 異なる記法がbuild.gradleファイルに混在しており、保守運用がやや複雑になってしまっている マルチモジュールの場合、複数のbuild.gradleに重複するコードを記述しなければならない バージョンアップする際に、変更箇所が散在してしまう どうしよう… Gradle君の悩みはどんどん大きくなった。 🧩 課題解決 課題解決のため、Gradle君はライブラリバージョン管理の改善を試し、二つ改善方法を見つけました: BuildSrc buildSrc/build.gradle に共通の部分を集約することができ、その中身は以前の build.gradle と同様の書き方です どのGradleバージョンにも使えます ただし、下記理由のため、一時対策として使われています 機能ごとの複数の依存関係をまとめるのは手間がかかります pluginsの集約はできません モジュール間のコンフリクトを完全に避けるのは難しい 拡張性に制限があります Version Catalog libs.versions.toml に共通のバージョン、依存関係、プラグインを集約することができ、その中身は以前の build.gradle とは異なります Gradle 7.4以上で導入された機能のため、バージョン制限があります Gradle 7.0 – 7.4 では Feature Preview、Gradle 7.0 以下では使えません 下記理由のため、恒久対策として使われています 機能ごとの複数の依存関係をまとめることができます [bundles] pluginsの集約もできます [plugins] Kotlin DSLのみで記述できるため、型安全などの利点があります Android Studioの新規プロジェクトでもVersion Catalogがデフォルトになったため、これからも業界の標準となっていくでしょう では、Gradle君と一緒にVersion Catalogを使ってみよう! 💻 Groovy DSL → Kotlin DSL → Version Catalog 移行作業には以下の2つのステップがあります: Kotlin DSLへの移行 下記ファイルの書き換え settings.gradle → settings.gradle.kts Groovy DSL → Kotlin DSL include ':app' ↓ include(":app") pluginManagementの追加 @Incubatingの警告が出る場合は、 @file:Suppress("UnstableApiUsage") も追加してください build.gradle(Project層) → build.gradle.kts(Project層) Groovy DSL → Kotlin DSL kotlin_version = '1.9.23' ↓ val kotlinVersion: String by extra("1.9.23") build.gradle(Module層) → build.gradle.kts(Module層) Groovy DSL → Kotlin DSL buildConfigの移行(AGP 9.0で廃止予定) Version Catalogへの移行 [versions] :すべてのライブラリのバージョンを集約します [libraries] :すべてのライブラリのパスを集約します BOMの集約もできます [bundles] :機能ごとの複数のライブラリをまとめます module (group:name) より group + name の方がおすすめです bundle = { group = "...", name = "...", version.ref = "..." } [plugins] :pluginsのclasspathとidを集約します [bundles] を活用すれば、必要な依存関係を1回のimplementationで一括して指定できます。 // libs.versions.toml [versions] room = "2.6.1" [libraries] androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } [bundles] room = ["androidx-room-runtime", "androidx-room-ktx"] // build.gradle.kts(Module層) ... dependencies { ... // Room implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) } そして、移行作業が完了したら、 Gradle Sync を忘れずに行いましょう。 💫 成果 これまでは各 Gradle ファイルに散在していたライブラリバージョン情報は、下記のようにVersion Catalogに集約しました: // libs.versions.toml [versions] activityCompose = "1.9.0" agp = "8.4.0" appcompat = "1.6.1" ... [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } ... [bundles] android-test = ["androidx-junit", "androidx-espresso-core"] androidx = ["androidx-core-ktx", "androidx-appcompat", "material", "androidx-constraintlayout-compose", "androidx-activity-compose", "androidx-lifecycle-runtime-ktx", "androidx-lifecycle-runtime-compose", "androidx-lifecycle-viewmodel-ktx", "androidx-lifecycle-viewmodel-compose", "androidx-navigation-compose", "androidx-datastore-preferences", "androidx-core-splashscreen", "androidx-swiperefreshlayout", "androidx-browser"] ... [plugins] android-application = { id = "com.android.application", version.ref = "agp" } dagger-hilt-android-plugin = { id = "dagger.hilt.android.plugin", version.ref = "hilt" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsGradle" } ... // build.gradle.kts(Project層) ... // buildscript {     ... repositories { google() mavenCentral() } ... } plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.dagger.hilt.android.plugin) apply false alias(libs.plugins.firebase.crashlytics) apply false ... } ... // build.gradle.kts(Module層) ... plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.plugin.serialization") id("dagger.hilt.android.plugin") id("com.google.devtools.ksp") ... } ... dependencies { // Kotlin implementation(libs.bundles.kotlin) // Compose implementation(platform(libs.androidx.compose.bom)) implementation(libs.bundles.compose) debugImplementation(libs.bundles.compose.debug) androidTestImplementation(libs.ui.test.junit4) // AndroidX implementation(libs.bundles.androidx) ... // Room implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) ... } 今後は Version Catalog の  [versions]  セクションにバージョン情報を集約しているため、ライブラリのアップデートと管理がより簡単になりました。 ぜひVersion Catalogの便利さを体験してみましょう! ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに 本番用Dockerfileには軽いからAlpine Linuxを使おう!という話はよく聞きますが、リスクがあるので何も考えずに使うのは避けましょう、というお話です。 Alpine Linuxを使う意味 イメージサイズの減少 「軽量」LinuxなのでこれがAlpine Linuxを使う最大かつ唯一の理由だと思います。 ただし現実的にイメージサイズに差があるか?というと、大きな差はないです。 debian-slim alpine ベース 28.77MB 3.09MB node:18 65.82MB 48.75MB python:3.12 46.7MB 19.59MB 確かにサイズ差はあるものの、その差は約20MB程度。 イメージサイズが影響するのは主にデプロイ速度ですが、この程度で現実的に影響が出るとは考えづらいです。 アプリケーションを載せるとそちらの方が大きなサイズを占めるため、より比率は低くなります。 Alpine Linuxの欠点 glibcがない ほとんどのLinuxディストリビューションは標準Cライブラリとしてglibcを採用していますが、Alpine Linuxはglibcを採用しておらず、代わりにmusl-libcを利用しています。 このため、以下のような問題を引き起こします。 バイナリが動作しない glibcに依存するバイナリは動作しない ex) 開発環境の豊富なDebianイメージでビルドしてAlpineイメージにコピーすると動作しない場合がある バイナリのビルドに失敗する glibcに依存するソースのビルドに失敗する PythonやNode.jsパッケージなどの内部でビルドするバイナリでも発生しえる パフォーマンス劣化の可能性がある glibcを使う場合より有意に低速になることがある 参考(少し古いですが…): https://superuser.com/questions/1219609/why-is-the-alpine-docker-image-over-50-slower-than-the-ubuntu-image 平たくいうと 互換性・パフォーマンスの担保ができない ということです。 これらの問題は個別に対応が必要で、例えばNext.jsのDockerサンプルでは互換レイヤーとしてlibc6-compatを別途導入しています。 https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile またパッケージやランタイムをアップデートした後に新しく問題が発生することもあるため、アップデート時のリスクを増やすことにつながります。 apkパッケージを固定できない DockerイメージにOS提供パッケージを追加する場合、動作の再現性のためにパッケージバージョンは固定したいですよね。 しかしAlpine Linuxでは実質的に パッケージバージョンの固定が不可能です 。 Alpine Linuxの採用するapk(alpine package keeper)ではパッケージバージョンを指定することができるので、これを使えば良さそうですが、、、 RUN apk --no-cache add git=2.43.0-r0 jq=1.7.1-r0 しかしAlpineのパッケージは 最新版しか維持されません 。 リポジトリを見ると、同一パッケージは基本的に1バージョンしかないことがわかります。 http://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/ このため、バージョンを指定してしまうとある日突然イメージビルドに失敗するようになります。 つまり固定は実質不可能です。 GitHub Actionsで使用していたサードパーティアクションがこの問題を踏んでいて、突然動かなくなることがありました。 開発環境として使いづらい Alpine LinuxはDebian等と比べるとOS提供パッケージが少ないです。 このため開発環境としてはUbuntu/Debianベースイメージが使われることが多く、本番用イメージとの差分が大きくなってしまいます。これにより本番イメージでのみ発生する問題が多くなってしまう危険があります。 どうするのが良いか debian-slimを使う 上記の通りalpineとdebian-slimベースイメージの差はわずかなので、基本的にdebian-slimを使うようにすると良いかと思います。 開発環境ではdebian(full)ベースイメージを使うことで利便性との両立も可能です。パッケージバージョンを固定する場合の問題も起こりません。 distrolessを使う よりアグレッシブにイメージサイズを削りたい場合は、distrolessベースイメージを使用します。 これはalpineよりイメージサイズが小さく、かつglibcを採用しているので互換性問題が起こりづらいです。 ただし パッケージマネージャが存在しない シェルすらない コンテナ内へのログインが不可能 という割り切りがされているので、マルチステージビルドしたアプリをコピーしてくるのが前提のイメージになります。 scratchを使う Go限定ですが、scratchを使う方法もあります。 Goで CGO_ENABLED=0 でビルドしたバイナリは単独で動作可能なので、何も入っていないscratchイメージに追加すればそれだけで動作します。 こちらもシェルが入っておらず、コンテナ内に入れないので注意が必要です。 まとめ Alpine Linuxについて、イメージサイズの減少幅の小ささ、それに対する種々のリスク・課題について解説しました。 debian-slimイメージが十分サイズが小さいため、長期的な運用において不確定要素を抱えるAlpineを採用する採用するメリットは薄くなっているのではないかと考えています。 特に開発用コンテナですでにdebianを使っているような環境では、debian-slimに乗り換えてみるのも考えてみてください。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに はじめまして。ニフティ株式会社の鹿野です。 この記事ではニフティのデータ基盤をご紹介いたします。 データ基盤とは データ基盤は、部署を横断して複数のデータを集約する場所です。 サービスやプロダクトを横断してデータを活用することで、顧客体験やビジネス価値を向上させることを目的として活動しています。 データ基盤の構成 採用している技術スタックを以下に示します。 Data integration Embulk Data transformation, catalog dbt Data lake Amazon S3 Cloud Storage 1 Data warehouse Amazon Redshift BigQuery 2 Data orchestration Digdag Data uses Tableau Redash 補足としまして、弊社のデータ基盤はETL・ELTパイプラインが混在する環境となっており、Embulkはこの両方に関与しています。 dbtを導入してからはETLの方が望ましいケースを除き、順次ELTへの移行を進めています。(dbtは特にデータモデルの管理機能がとても素晴らしいと感じています。) また、上記構成図には出てこない部分として、データウェアハウスへのデータロードのほとんどはAmazon Reshift Spectrum が担っています。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する Google Cloudのオブジェクトストレージサービス ︎ Google Cloudのデータウェアハウスサービス ︎
アバター
インナーソースコミュニティであるInnerSource Commons Japanが2024年8月8日にInnerSource Gatheringを日本初開催いたします。 イベントの詳細、参加につきましては下記ページを参照ください。 https://gatherings.innersourcecommons.org/tokyo-2024/ 当社では、今回運営参加する基幹システムグループの芦川、小松を中心にインナーソースの推進活動をしております。活動の様子につきましては、 インナーソースを導入してみた その① お試し導入編 をぜひご一読ください。 また、芦川がチームリーダーをしているサービスインフラチームの紹介記事もあります。インナーソースのような活動を尊重するチーム文化が窺い知ることができますので、こちらも合わせてお読みいただけると幸いです。 基幹システムグループ サービスインフラチームの紹介です
アバター
はじめに 先日、Amazon RDS for MySQLのマイナーバージョンアップグレードに失敗したのですが、調べても情報が見つからなかったので情報共有のために記事にしました。 経緯 先日、AWSからこんな通知がありました。 [アクションが必要です] Amazon RDS for MySQL は、2024 年 3 月 29 日にマイナーバージョン 8.0.31、8.0.29、8.0.28 を廃止します | [Action Required] Amazon RDS for MySQL is deprecating minor versions 8.0.31, 8.0.29 and 8.0.28 on March 29, 2024 [AWS Account: xxxxxxxxxxxx] [AP-NORTHEAST-1] 今使っているMySQLのバージョンが廃止されるため、期日までにアップグレードが必要という通知です。 特に引っかかることもなかったのでアップグレードしました。 しかし、開始してしばらく待ち続けたのですがなかなか終わらず、1時間ほど経ったときに Database instance is in a state that cannot be upgraded: PreUpgrade checks failed: RDS detected incompatibilities when upgrading to MySQL 8.0.36. For details, see the PrePatchCompatibility.log file in the Logs section. というメッセージが表示されました。 そのまま訳すと「MySQL 8.0.36へのアップグレード時にRDSが非互換性を検出したのでアップグレードに失敗しました。詳細はPrePatchCompatibility.logを見てください。」という内容のようです。 しかしPrePatchCompatibility.logというログは生成されていませんでした。 今回は8.0.31から8.0.36へのマイナーバージョンのアップグレードなので互換性に問題が起きることはあまりなさそうなのですが…。 エラーログ(mysql-error-running.log)を見ると [ERROR] [MY-013180] [Server] Function ‘auth_socket’ already exists. というエラーが出ていましたが、いまいち原因の特定はできませんでした。 原因 調べても手掛かりとなるような情報はなく原因が分からなかったのでAWSサポートに問い合わせてみました。 その結果、 テーブルへのトランザクションが実行中だった ことが原因と分かりました。 アップグレード前に互換性検証のために対象DBの全テーブルに対してロックを取得しているらしく、その際にトランザクションが実行中のテーブルがありロックに失敗していたようです。 対策 対策としてはアップグレードする際に 一旦アプリケーションを停止する プロセス停止、ロールバックなどでトランザクションを終了する などしてトランザクションがない状態でアップグレードする必要があります。 結果 その後、失敗した2日後に再度アップグレードをしたところ、無事に成功しました。 今後はアプリケーションを停止するか、トランザクションがないことを確認してからアップグレードしたいと思います。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに こんにちは。ニフティ株式会社の添野です。 AWS Amplify で配信している静的ページに対する定期リビルド機構を組む必要があったので、その際に Amazon EventBridge API 送信先 を利用した話を共有します。 背景 社内のとあるサービスで利用する静的ページの配信基盤にAWS Amplifyを採用しており、そのページの中には定期的に変わるコンテンツが存在します。その仕様のため定期的にリビルドすることが必要になりました。最初は AWS Lambda を利用してAWS AmplifyのIncomingWebHookを定期的に叩こうかなと思いましたが、AWS Lambda上のコードの管理やAWS LambdaランタイムのEOL対応、AWS Lambdaに対するメモリ設定など気にする項目が増えて、手間だなと思いました。そこでもう少し良さげな機能が無いかなと思い探してみたら、 Amazon EventBridge の中にあるAPI 送信先を見つけました。 API 送信先とは? 公式ドキュメント によれば、API送信先(あるいはAPI Destinationとも呼ばれる)とは、 AWS サービスまたはリソースをターゲットとして呼び出す方法と同様に、ルールのターゲットとして呼び出すことができる HTTP エンドポイントです。 とのことで、レートリミットなどの制御やセキュリティの機能なども提供します。 API 送信先を利用した定期リビルド機構について ここではTerraformのコードを示しつつ、組んだ話について説明します。 まずは、対象となるAWS Amplifyのアプリケーションに対して、Incoming WebHookを設定します。叩くとAmplifyでビルドと公開処理が走ります。 resource "aws_amplify_webhook" "master" { app_id = aws_amplify_app.static_content.id branch_name = "master" description = "triggermaster" } 次に、毎日07:30に定期的にリビルドを走らせるルールを作成し、ターゲットとしてAWS AmplifyのIncoming WebHookに前置するAPI送信先を指定します。ここで、Amazon EventBridgeの接続先(aws_cloudwatch_event_connection)では提供されている認証方式(APIキー認証、Basic認証、OAuth認証)を必ず選択しないといけない点に注意する必要があります。今回はBasic認証を選択することにしました。 resource "aws_cloudwatch_event_rule" "regular_rebuild" { name = "${var.application_name}-regular-rebuild-${var.environment_name}" description = "${var.application_name} Regular Rebuild ${var.environment_name}" schedule_expression = "cron(30 22 * * ? *)" # NOTE: 毎日 07:30 JST (静的ページリビルド) } resource "aws_cloudwatch_event_target" "regular_rebuild" { target_id = "RegularRebuild" arn = aws_cloudwatch_event_api_destination.regular_rebuild.arn rule = aws_cloudwatch_event_rule.regular_rebuild.name role_arn = aws_iam_role.regular_rebuild.arn } resource "aws_cloudwatch_event_api_destination" "regular_rebuild" { name = "${var.application_name}-regular-rebuild-${var.environment_name}" description = "${var.application_name} Regular Rebuild ${var.environment_name}" invocation_endpoint = aws_amplify_webhook.master.url http_method = "POST" invocation_rate_limit_per_second = 1 connection_arn = aws_cloudwatch_event_connection.regular_rebuild.arn } resource "aws_cloudwatch_event_connection" "regular_rebuild" { name = "regular_reguild_webhook" description = "A connection description" authorization_type = "BASIC" # NOTE: Amazon EventBridgeが利用するBasic認証情報 auth_parameters { basic { username = "user" password = "Pass1234567!" } } } 実際に定期リビルドされている様子を示します。Updatedが07:31、Last commitがIncoming WebHook起動であることが示されています。 料金(東京リージョンを仮定する)は、 API 送信先 を利用する方式では0.24USD/100万呼び出しであり、対して AWS Lambda を利用する方式では、メモリを128MB指定して、呼び出しに300ミリ秒が掛かると、0.0000000021 x 300USD/呼び出しであり、AWS Lambdaを利用する方式の方が若干高くなります。 おわりに 今回は、API 送信先を利用してAmplifyで配信している静的ページに対する定期リビルド機構を組んだ話を共有しました。AWS Amplifyのリビルド機構を組みたい方の参考になれば幸いです。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに こんにちは。ニフティ株式会社の並木です。 今回は「Zapier」でAPIを呼び出し、実行結果をSlackに通知する方法についてご紹介いたします。 背景 以前「 Zapierを使ってRSSフィードの更新をトリガーにしたTwitterへの自動投稿機能を作ってみた 」で紹介した方法で、RSSフィードの更新をトリガーにしたX(旧Twitter)への自動投稿機能を作成したのですが、上記記事の「④Twitterに文章を投稿したい」で紹介した機能が2023年9月に使用できなくなってしまいました。(詳細: https://help.zapier.com/hc/en-us/articles/18657531069965 ) 別の方法で自動投稿機能を復活できないかという話になり、前から使用していた「Zapier」を流用しつつ、新たに「Amazon API Gateway」「AWS Lambda」「X API」を使用して、自動投稿を実現することにしました。 構成図は以下の通りです。 構成図の【1】【2】については、「 Zapierを使ってRSSフィードの更新をトリガーにしたTwitterへの自動投稿機能を作ってみた 」の①~③の処理をそのまま流用します。 【4】の手順は「 API GatewayとLambdaでX投稿するAPIを作ってみた 」に記載しています。 【5】の手順は「 LambdaでX APIを呼び出してみた 」に記載しています。 「【3】ZapierからAPI Gatewayを呼び出す」と「【6】SlackにXの投稿結果を通知」が今回の説明範囲になります。 前準備(必要な材料をそろえる) Zapierを使用するにあたって、必要となるものを準備します。 ■ 呼び出すAPIのURLを取得する 「 API GatewayとLambdaでX投稿するAPIを作ってみた 」で作成したAPIのURLを取得します。 URLの取得方法は公式ドキュメントの「 API の呼び出し URL の取得 」に記載があります。 画像の赤枠部分をコピーすればOKです。 ■ 呼び出すAPIのAPIキーを取得する 「 API GatewayとLambdaでX投稿するAPIを作ってみた 」で作成したAPIのAPIキーを取得します。 API Gatewayのコントロールパネルの左メニューの「APIキー」から確認できます。 「Zapier」から「Amazon API Gateway」を呼び出す 前準備で取得したURLとAPIキーを使って、ZapierからAPIを呼び出します。 ■ App&Event Appは「Webhooks by Zapier」、Eventは「POST」を選択します。 ■ Action URLには、前準備で取得したAPIのURLを入力します。 「 API GatewayとLambdaでX投稿するAPIを作ってみた 」の中で、JSON形式で「hoge」というリクエストパラメータをAPIで受け取れるようにしました。 なので、Payload Typeは「JSON」を選択します。 Dataには「hoge」というキー名と、Xにポストする文章を設定します。 認証周りですが、Basic Authは今回使わないため空欄でOKです。 今回はAPIキーによる認証を行うため、Headersに「x-api-key」というキー名と、前準備で取得したAPIキーを設定します。 これでZapierからXに投稿できるようになりました! SlackにXの投稿結果を通知する 「 API GatewayとLambdaでX投稿するAPIを作ってみた 」で作成したAPIから、以下の通りJSON形式で値が返却されます。 { "statusCode": 200, "headers": { "Content-Type": "application/json" }, "body": { "result": "Xの投稿結果", "api_response": "XAPIからのレスポンス内容" } } 上記の中から "result": "Xの投稿結果" を取り出してSlackに投稿し、Xの投稿の成功/失敗を通知できるようにします。 ■ App&Event、 Appは「Webhooks by Zapier」、Eventは「POST」を選択します。 ■ Account 自分のSlackアカウントを選択します。 ■ Action Channelに通知したいチャンネル名を選択します。 MessageTextに通知したい内容を入力します。ここに "result": "Xの投稿結果" を埋め込みます。 これでSlackにXの投稿結果が通知されるようになりました! おわりに 「 Zapierを使ってRSSフィードの更新をトリガーにしたTwitterへの自動投稿機能を作ってみた 」で紹介した方法が、2023年9月に使用できなくなり焦りましたが、無事に自動投稿機能を復活させることができ一安心しました。 APIを自作することで、以前は無かった「Xの投稿結果を通知する」という機能を付けることができたので、結果的に良かったのではないかと思います。 参考記事 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-call-api.html#apigateway-how-to-call-rest-api ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに こんにちは。ニフティに新卒で入社して多分6年目の佐々木です。今回はAWSのサービスの一つである EC2 についてコスト削減を行う方法を紹介します。 以前ご紹介したコスト削減手法ついては、以下のブログ記事をご参考ください。 CloudWatch編 S3編 CloudFront編 背景 ニフティではサービス基盤にAWSを活用しており、コスト削減のためにサービスのインフラ効率を追求する取り組みや、有志で取り組んでいる社内でのコスト削減勉強会なども実施しています。 今回は、EC2のコスト削減においてのポイントや注意点について簡単にご紹介したいと思います。 この記事の内容 触れること EC2のコスト削減方法 EC2のコストを削減する上での注意点 実際に試した社内でのコスト削減事例とTips 触れないこと EC2自体の詳細の説明 詳細のインフラ構成(コスト削減が目的のため、今回は一般的なインフラ構成のご紹介に限らせていただきます) EC2の基本 EC2(Amazon Elastic Compute Cloud)は言わずとしれたAWSの代表的なサービスです。 必要に応じて容易にサーバーの追加・削除が行える仮想サーバーの役割を果たしています。 EC2のコスト削減 課金形態 さっそくEC2のコストを確認していきましょう。 2024年4月現在では主に以下のようになっています。(ap-northeast-1の場合で無料利用枠は除きます) EC2自体の主な課金要素は最低限この3つを押さえておけば問題ないかと思います。(他にも様々な細かい課金要素はあります) インスタンスタイプ 利用するEC2のインスタンスタイプにより課金される金額が変わります 例えば、t3.microの場合は毎時0.0104USDの課金が発生します データ転送 「送信(アウト)」に対して課金が発生します。受信(イン)には課金されません 例えば、最初の 10 TB/月の場合はUSD 0.114/GBの課金が発生します 料金プランによる課金形態 後述するEC2の料金プランを選択することで様々な割引を受けることができます 詳細はこちらの AWS公式の資料 をご参考ください。 コスト削減方法 インスタンスタイプの見直し 当然ですが、どの場合においてもサービスの要件を満たす最低限のスペックのインスタンスであればコストを最小限に抑えることができます。 こちらは後述する「インスタンスサイズの見直し」の項で実際の削減例を交えて詳しく紹介しようと思います。 インスタンスのリソースの増減を考慮する こちらは起動しているインスタンスの数を状況に応じて変更することでコストを抑える方法です。 実際の稼働しているサービスによっては、繁忙期の利用増加や時間帯ごとの需要の変化などが生じる場合があると思います。そのような場合はピークの時間帯に備えて常にフルでサーバーを稼働しておく必要がない場合もあります。 そのような場合は、 Auto Scaling を組むことで正常なインスタンスの数を維持しつつトラフィックの増減に合わせてサーバー台数を自動で変化させることができます。また、時間台によって起動させるサーバーの台数を予め指定することもできます。 また別の要因として、開発環境であればそもそも利用する時間帯が限られているかと思います。そちらの場合には、夜間休日にインスタンスを自動停止するようなスクリプトを組むことで無駄なコストの発生を抑えることができます(塵も積もれば山となる…)。 EC2インスタンスの自動停止・起動方法については、Lambda関数を活用して組むことができます。 https://repost.aws/ja/knowledge-center/start-stop-lambda-eventbridge EC2の料金プランによる費用最適化 課金形態の3つ目「料金プランによる課金形態」にあたります EC2にはいくつか料金プランがあり、特定の条件下で利用することでサービスの料金が割引になる仕組みが存在します。 Amazon EC2 オンデマンド 従量課金制の料金形態 デフォルトでインスタンスを立てると通常この費用が発生する Amazon EC2 リザーブドインスタンス(RI) 「一定期間の継続 」を契約することで大幅な割引(最大72%)ができる料金形態 1年間または3年間の期間存在する、当然3年間の方が割引率は高い 「キャパシティの予約」という形で契約を行うことで、任意のインスタンスタイプのEC2インスタンスを予約期間中は自由に起動できるようになる 2つのプランが提供されている スタンダードRI: 特定のインスタンスタイプについての割引が受けられるプラン 割引率は高いが原則途中で変更はできない コンバーチブルRI: インスタンスファミリー・インスタンスタイプ・プラットフォームなどの変更が可能 なプラン 割引率はスタンダードRIに比べると低。 ただし元のコンバーチブルRIと同等価格以上のインスタンスタイプでなければ交換することができない 1年間または3年間の期間存在する、当然3年間の方が割引率が高い 3種類の前払いの支払いオプションが提供されている 全額前払い 一部前払い 前払いなし 常に稼働させておく必要があるシステムの場合に有用 Savings Plans 「 一定期間の継続、一定量の使用 」を契約することで大幅な割引(最大72%)ができる料金形態 「コミットメント額 = SPの料金として支払う額」という形で契約を行うことで、使用量に対する割引が受けられるようになる リザーブドインスタンスと比較して 高い柔軟性 がある 2つのプランが提供されている Compute Savings Plans 割引率はやや低いが柔軟性があるプラン インスタンスファミリー、サイズ、アベイラビリティーゾーン、リージョン、OSに関わらずEC2インスタンスの使用に自動的に適用される EC2 Instance Savings Plans 割引率が高いがやや適用条件があるプラン アベイラビリティゾーン、インスタンスタイプ、OSに関わらずそのリージョン内で選択されたインスタンスファミリーのコストを削減できる Amazon EC2 スポットインスタンス AWS側の空いているキャパシティを活用することで大幅な割引(最大90%)ができる料金形態 空きがなくなると中断が発生する可能性があるので、処理が途中であっても中断可能なサービスや機能に適していると言える 結局どう選べば良いのか? EC2の料金プランは種類も多く条件もやや複雑です…。 なので個人的にはまずはオンデマンドを利用し、実際に少し稼働させてみて想定したスペックで耐えられることが確認できたら、要件に合わせて割引可能なプランの選択を検討する、といった流れが良いのかなと思います。ひとまずは、実際のコンソール画面を見ながら料金プランのイメージを掴めると良さそうです。 また余談として、SP/RIはAWS Organizationの管理アカウント単位で買うこともできるのですが、こちらも残念ながら非常にややこしい仕様となっています…。ただ、上手く活用することで各プロダクトチーム単位で買うよりも大きなメリットがあるので、できれば検討したいところです。 管理アカウントでの購入について以下の記事で詳しく解説しているので、良ければ合わせてご参照ください。 AWS Organizationの管理アカウントでSPs/RIを一括購入してカバレッジを上げる 実際に社内で試したこと インスタンスサイズの見直し 開発環境にあるEC2インスタンスのスペックが過剰なものがあり、そちらの見直しを行いました。 方針 事前にインスタンスタイプについての知識があまりなかったので、いきなり作業に取り掛かる前に以下の方針を立てました。 使用量を調べて、全体的にインスタンスタイプを下げても問題ないかを把握する CPU使用率 を見て上限に張り付いていないかを確認する メモリ使用率はCloudWatch Agentを導入しないと把握できないので考慮からは除外 同じインスタンスファミリー内で、ベースライン使用率を超えないと思われるスペックを選択する 世代が新しいほどコストパフォーマンスが向上する傾向があるため、なるべく最新のインスタンスファミリーを選ぶ ただし「汎用バースト型のT系」から「グラフィック処理に特化したG系」にするなどアーキテクチャそのものの変更なので、要件によってそもそも変更不可能な場合がありその点を考慮する アプリケーションやミドルウェアの要件によって世代が新しいインスタンスを利用できない可能性がある (詳細は割愛しますが、ミドルウェアのバージョンが古いとG系で扱う ENAドライバ に対応していない場合があり、今回それに該当しているものがありました…) 洗い出し 上記の方針を元に、各インスタンスで変更できそうなスペックの候補を洗い出していきます。 今回はNotion DBを使って以下のような表を作りました。 各インスタンスファミリーの特徴については 公式ページ を見ながら把握していきます 今回はほぼT系からT系への変更だったこともあるので、T系インスタンスが前提の話になります。 現行スペックと変更後のスペックの対比として今回は以下の項目を考慮に入れています 各インスタンスタイプの料金 vCPU CPU使用率(%) ベースラインパフォーマンス(%) 前提として、T系インスタンスでは CPU クレジット を絶えず一定の割合で獲得しています。これによりある程度負荷が急増した際にも蓄積されたクレジットを消費することで、ベースラインレベルを超えてCPU使用率をバーストできるという仕様になっています。 「通常はベースラインまで性能を落として上で起動しておいて、緊急時には100%の性能で稼働する」というとっても便利な機能ですね。 また、 公式ドキュメント にもありますが、EC2インスタンスではベースライン使用率(ベースラインパフォーマンス)というものが定義されています。「CPUクレジットの獲得数とCPUクレジットの使用率が一致する場合に、正味のクレジットの残高が0の状態でCUPを使用できるレベル」とのことです。つまり「このラインの使用率を超えるといずれクレジットが枯渇するよ、枯渇したらこのレベルまでしか性能を出せないよ」というのを数値化したものだと思われます。 今回のT系インスタンスの場合では、このベースライン使用率を超え始めるとクレジットを消化し始める(=パフォーマンスが悪化する可能性がある)ので、CPU使用率が常にこの値を下回るのであればスペックを下げても問題ないと考えました。 しかし、インスタンスタイプを変更するに当たってもう一つ罠があります…。 簡単に言うと、vCPUとCPU使用率(CloudWatchのコンソール上で見れる数値)には反比例の関係性があるようです。 CPUクレジットの概念 の項にもありますが、1クレジットあたりの定義が以下になっています。 1 CPU クレジット = 1 vCPU × 100% 使用率 × 1 分 1 CPU クレジット = 1 vCPU × 50% 使用率 × 2 分 1 CPU クレジット = 2 vCPU × 25% 使用率 × 2 分 上記の計算式から、今回は「インスタンスタイプを変更してvCPUが半分の値になった場合はCloudWatchメトリクス上で表示されるCPU使用率は倍の値になる」と見積もって計算を行いました。 また、同じく CPUクレジット獲得 の項では、「表内のベースライン使用率は vCPU 別の割合です。CloudWatch では、CPU 使用率は vCPU 別に表示されます」とあるので、そう読み取って良さそうです。 またここでは考慮していませんが、実際にはインスタンスの世代による差(新しい世代だとパフォーマンスの部分で改善されていたりする)もあるので、そちらも影響してくると思います。 変更 最後に先ほど決めた変更後のスペックに手動で変えていきます。 今回は開発環境なのでダウンタイムを考慮せずに簡単に変えることができました。 これで削減完了です。 徐々に見直しを行った結果以下のようにEC2の使用量を半分以下に減らすことができました! Cost Explorer などで実際に削減されているのを見るとモチベーションが上がって嬉しいですね。 まとめ 今回インスタンスタイプの見直しやEC2の割引仕様を調べるにあたっては、ドキュメントから判別がつかない点も多く、実際の事例を参考に多分こういう仕様だろう、という推定をしながらとりあえずやってみる部分がそこそこありました。 一通り各インスタイプの特徴や割引プランを把握しておくことで、実際の運用にも役立てると思うので、まずはドキュメント化などをしつつチーム内での認知不可を下げて、少しずつ社内の共通知にしていきたいなと思っています。 検証環境とはいえ、初めてインスタンスタイプを変えたときはこれでよいのかな?と不安になったので、実際に変更したり買って学べる環境があると良いなと思います。 We are hiring! ニフティでは、さまざまなプロダクトへ挑戦するエンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトよりお気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も受け付けています! カジュアル面談 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering ニフティ株式会社 – connpass
アバター
はじめに 「春からエンジニアだけど何もわからなくて不安。どんな準備をしたらいいのだろう?」 そんなお悩みを抱えている人達に向けて、ニフティのエンジニアが書き上げたブログの中から新米エンジニアに是非読んで頂きたいブログを厳選しました。 このブログには以下の内容が書かれています。 新米エンジニアにおすすめな汎用的な技術書 業務効率化方法 新人エンジニアとしての心構え   【初心者向け】エンジニアになりたい/なりたての人におすすめの本3選 【初心者向け】エンジニアになりたい/なりたての人におすすめの本3選 こちらのブログではネットワーク、DB、Linuxといった様々な観点で初学者向けの書籍を紹介しています。 特定の言語に依存しない汎用的な内容なので、まずはこれらの中から何か手に取ってみるのはいかがでしょうか。 技術初心者の私が業務中に得た、仕事の効率化の方法の話 技術初心者の私が業務中に得た、仕事の効率化の方法の話 当時入社1年目だった社員が実務を通して役に立った業務効率化Tipsについてまとめているブログです。 ツール導入だけでなくCLIのコマンド単位のTipsもあります。これは!と思ったものがあれば明日から取り入れてみましょう。 効率化小ネタ集 効率化小ネタ集 以下のような便利なツールの導入方法や活用方法についてまとめています。 アプリケーションの定期実行を可能にする「タスクスケジューラ」 特定のページからのみ検索を行う「サイト内検索」 現在開いているウェブページに対してJavaScriptの処理を適用する「ブックマークレット」 これらは応用が効く内容のため、覚えたら早速業務に活用できるかもしれません。 新人のすゝめ ~文系卒がエンジニアとして過ごした入社1年目の気付き~ 新人のすゝめ ~文系卒がエンジニアとして過ごした入社1年目の気付き~ 新人エンジニアの時に感じた不安や悩みを語りつつ、そこから今に至るまでに得た気付きを語っています。 入社して間もない時期は仕事の進め方や質問の仕方などで困っていませんか? そういった場面でこちらのブログに書いてある内容を思い出してみてください。   トラブル対応で私はいっぱい成長してきた トラブル対応で私はいっぱい成長してきた 長年エンジニアをしていた社員が、「エンジニアの学習と成長」について自身の経験を通してお話しています。 学習を行っていく中で何を大切にすべきか、成長できる人はどういった状況にある人なのか興味がある場合は是非読んでみてください。   さいごに 今回は新米エンジニアに向けたブログたちを紹介しました。 興味を引く点や新たな発見はありましたか。 NIFTY engineeringでは様々な技術の話題・情報を取り上げています。 どれも素敵なブログばかりですので是非覗いていってください。 このブログが、新たな挑戦を始めるための一歩になればと考えています。 また、貴方がエンジニアの一人としてご活躍できることを心から祈っております! ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
マイ ニフティ というニフティ会員向けアプリのチームで、スクラムマスターを務めている西野です。 スクラムマスターとしてPO(プロダクトオーナー)のサポートが十分にできているか課題を感じており、去年PO向けの研修を受けてきました。研修中、POとして振る舞ってみると「意思決定の怖さ」を感じるシーンが多くあり、それをきっかけとして「効果的な意思決定」について考えてみました。 POでなくとも、小さなものから大きなものまで何かを決定するときに、少し不安な気持ちになったりしませんか。 例えば、外食時のメニュー選択は、意思決定の個人的な傾向が現れる一例です。自分はわりとすぐにメニューを決められるのですが、注文を取る時に突然変更してしまい、友達に「ええ……?」と言われるタイプです。 マイ ニフティでは、意思決定の際に少しずつデータ面での理由づけができるような環境を整えてきました。今回は、データでどのように意思決定を行うかについて紹介します。 意思決定のプロセス なんとなくする意思決定も、理由をつけてする意思決定も、等しく意思決定ではあります。意思決定には様々な方法がありますが、代表的なものについて挙げてみました。まずはそれぞれの特徴を把握しましょう。 直感 合理的意思決 多数決 コンセサスをとる データドリブンで行う 直感で決める 「肌感でこっちがいい」「えいやで決めましょう」「経験上こちらがいいと思う」というものです。 GOOD 即断できる BAD 意思決定者のバイアスがかかる 周囲への説明や説得が難しい 決断者も自分を説得しきれておらず、選ばなかった選択肢で迷いだす 効果があっても再現性がない 直感は、経験者が一番多い意思決定方法といえます。BADポイントも多いですが、最短で意思決定ができるメリットが大きく多用されがちです。 強力なリーダーシップ下でないと機能しにくく、もし機能してもそのリーダーがいなくなると意思決定ができなくなってしまうという課題があります。 合理的意思決定 すべての選択肢を比較検討し最良を選ぶ方法です。 GOOD 再現性が高い 関係者への説得がしやすい BAD とても時間がかかる すべての選択肢を出せる情報があるとは限らない 比較検討の過程で「どのような理由で選択に至ったか」という理論がうまれるため、もしその意思決定が効果的だった場合、その成功は再現できる可能性が高いです。 しかし、現実問題として「すべて」の選択肢を出せたという証明の困難さ、それを出すまでにかかる時間は膨大になりやすいです。また、ここまでの合理性を求められるような意思決定のシーンは限られます。 多数決 多数決原理です。賛同者が多い方で決定します。 GOOD 即断できる BAD 多数決に参加していない人への説明や説得が難しい 再現性がない 少数派の意見が無視されてしまう 多数決で決める際に重要なことは、関係者全員がその場に参加していることです。もし一部の人だけで決をとり「多くがそう思った」という説明をしたとして、関係者は納得がいくでしょうか。 選択の理論がないので、メンバーの入れ替わりや状況・気分などが影響し、うまくいったときの再現性もありません。 少数意見を支持した人との分断を招いてしまうことも課題です。 コンセサスをとる 関係者全員の合意形成をします。 GOOD 関係者の分断が起きない 合意をとる課程である程度の理論が生まれれば、再現性がある BAD 時間がかかる コンセサスをとるべき範囲を決めにくい みんなが納得するまでやりきることができればいいですが、そうではない場合、妥協による同調を招きやすいです。 もし納得できるまでやりきれたとしても、合意してない人・すでに合意している人の間に温度差がうまれ、モチベーション維持が課題となります。 データドリブンで行う データを起点として意思決定を行います。 GOOD データを正確に解釈できれば再現性が高い 関係者への説得がしやすい(客観性をもちやすい) BAD 一定の質のデータを事前に集めないといけない データを正確に解釈しないと再現性が低くなる 革新的なアイデアは出にくい ユーザーの行動データなどを元にした意思決定方法です。 データを恣意的に解釈してしまわないかといった課題はあるものの、データソースを開示していればそれが正しいかという検証も可能です。 意思決定の根拠がはっきりとしているので、意思決定後の迷いや変更がおきにくい利点があります。 意思決定のポイント 意思決定は、プロダクトやチームを良い方向にも悪い方向にも動かすことがあります。何かを動かすためには、意思決定を行うことが重要です。しかし、頻繁に意思決定を変更することは、プロダクトやチームを不安定にする可能性があります。 しかし、理由もなく頻繁に意思決定を変更するような行為は、プロダクトやチームを崩壊させる方へどんどん動かしてしまいます。 意思決定の方法をいくつか紹介しましたが、意思決定のポイントは「選んだ理由」だけでなく、それ以外の選択肢を最良だと「選ばなかった理由」がわかるかどうかを重視すると良いでしょう。 データドリブンが意思決定に有効だと思う理由のひとつに、決定に至る論理の立てやすさがありますが、最も効果的な点は「意思決定を変えにくい」ことにあると思います。 人の意思というものは、想像以上に脆く、変わりやすいものです。そのために、主観ではなく客観的な情報をひとつでも多く持っておくことで、決定の変化や選ばなかった選択肢について思い悩むことを防ぐことができます。 データを集める、共有する では、実際にニフティの会員向けアプリである「マイ ニフティ」で、どのようにデータドリブンによる意思決定を行なっているかを紹介したいと思います。 基本データ「のみ」をダッシュボードに表示する アプリをリリースする前の時点、つまりデータを集める前の意思決定は、実データに基づいた判断ができません。現実が見えていない段階では、あまり確認するデータ範囲を欲張らないことが大切です。 データ収集を開始するにあたり、最も重要なのは、必要最低限の条件を満たしているかを確認することです。 データ収集が可能な必要最低限のデータを特定できるか ダッシュボードの内容が必要最低限の範囲に絞られているか 上では必要最低限のデータと書きましたが、本当はリリース初期から可能な限りデータの蓄積をしておいたほうが良いです。これは、適切なデータの欠如が正確なデータ解釈を妨げ、効果的な意思決定を遅らせる可能性があるためです。 しかし、どれだけ網羅的にデータを収集しようと努力しても、リリース後には必ずと言っていいほど必要なデータが不足していることに気づきます。 いち早くデータ計測を開始することと、リリース前にデータ収集点を網羅することは相反することに注意してください。適切な妥協点を探る必要があります。 データは意思決定に活用されるため、意思決定者だけでなく、関係者全員にとって意味のある指標として可視化されるべきです。 マイ ニフティの場合、最低限の可視化として、ログインユーザーの累計と日・週・月のアクティブユーザー数をダッシュボードに表示することに決めました。 ダッシュボードに表示する内容を決めるコツは「もしかしたらいるかも」レベルの情報は省いてしまうことです。プロダクトの主要KPIや、成長にとって本当に必要な数字だけに絞ります。 画面表示やボタンタップのイベントなども取っていますが、初期段階では毎日見るダッシュボードにはあえて載せていません。毎日見る必要があるデータと、意思決定をするために必要なデータは粒度や質が異なるからです。 フィードバックを反映する いったん最低限の情報が見られるようになったダッシュボードができたら、それを関係者に見せ、デイリーで知りたい情報についてフィードバックをもらいます。 フィードバックは、ダッシュボードの運用開始後、主要メンバー全員が1-2週間は毎日見たうえでの要望を採用したほうがより効果的です。 私たちスクラムチームの場合、デイリースクラムでダッシュボードを毎日見て、気づいたことを共有するようにしています。 例えば、デイリーでアクティブユーザーを観察する中で「回線の契約コースによってユーザー行動が異なるのでは」というフィードバックがありました。 コース別の継続率をダッシュボードに追加してからは、会員全体向けの施策だけでなく、コース別のニーズに応えるための施策も動き出した実感があります。 (なかなか見せられない情報が多いので、ダッシュボードの一部分の雰囲気だけ……) ダッシュボードを成長させる ダッシュボードを活用することで、データドリブンによる意思決定プロセスが促進されます。 プロダクトの成長や施策の効果を視覚的にリアルタイムで評価し、継続的な改善につなげることができるからです。 継続的な改善施策において、チームは施策立案時にその施策が数値で効果測定可能であるかを確認し、ダッシュボード上でその施策効果が可視化されるようにします。 ダッシュボードは、施策やプロダクトの成長や方向性とともに項目の追加・削除を行い、プロダクトの現状に適合するように管理し続けます。 ダッシュボードの導入・運用フロー図 ここまでの内容をフロー図にするとこうなります。 データドリブンを始めるための工夫 データドリブンは、新規プロダクトの立ち上げや、施策が少ない段階では比較的受け入れやすいです。 一方で、データがあまり取れていない既存プロダクトに今からデータを追加したい場合、「やりたいことがいっぱいあるのに、ユーザーから見えないデータ部分の優先度は上げられない」とPOに受け入れられないケースもあるかもしれません。 この場合、意思決定者は「自分はユーザーを理解しており、データなんかなくても意思決定はできる」と思っています。しかし本当は、施策を絞りきれず、何をしないかを決定できてない状態です。 開発者として、この状態を受け入れていいかは注意しましょう。主観による意思決定は、施策Aをやっている途中で、やっぱり施策Bがいいかも、と言い出しやすい環境をつくっています。 やりたい施策がどれだけたくさんあっても、10個の施策のうち、あえて9個を失敗させたい人はいません。 データがあることで、成功する施策だけでなく失敗する施策もある程度パターン化できます。やらなくてもいい施策を潰し、やるべき施策にいち早くフォーカスするためにも、データ収集と見える化はセットで必要です。 データに基づく意思決定の効果 意思決定のプロセスそれぞれに利点と欠点がある中で、データドリブンによる意思決定の意義は何でしょうか。 開発者は、POなど意思決定者の決断が効果的になるよう、データ収集とビジュアライゼーションで支援することができます。また、意思決定者が決断後に「やっぱり……」と迷わせない仕組みをつくることは、開発フローをシンプルにし、開発者自身の環境を守ることにも繋がります。 データドリブンを開始すると、よく「分析を専門的に学習したわけではないので、データを扱う自信がない」という不安の声を聞きます。 たしかに、データ分析の専門性が高ければデータ解釈の精度が高まります。しかし、どれだけ高い専門性をもっていたとしても、常に正しい意思決定ができるわけではありません。 データ分析の専門家でなくても、データを効果的に活用することは可能です。「後戻りをしない」「後から迷わない」ための意思決定であれば、もし迷う気持ちがあってもデータがその決定の妥当性を裏付けてくれます。 データを活用した戦略的アプローチ ここまでデータの重要性を語ってきましたが、データを集めるだけで自ずと効果的な施策になるわけではないことに注意してください。 データを効果的に活用するためには、データの解釈と仮説の検証、ひいてはPOと開発の協力が不可欠です。 POが戦略を立てる際は、事実と推測を分けることに気をつけてください。そしてその推測をできるだけ推測でなくなるように検証し、プロダクト戦略を組織の目標と合致させていきます。 開発チームは、ABテスト、アンケート回答内容といったさまざまなデータを組み合わせることで、POの仮説検証を支援します。 このような多角的なデータ分析により、推測の精度を高め、戦略の破綻リスクを軽減することが可能です。 データドリブンなアプローチを成功させるには、単にデータを集めるだけでなく、そのデータを基に仮説を立て、検証し、そして戦略を調整するという継続的なプロセスが必要です。 データを基に、明確で説得力のある意思決定を行うことでプロダクトの成長に繋げていきましょう! 私たちマイ ニフティチームも、データドリブンによる意思決定を行なっています。2024年4月現在、アプリエンジニア(特にiOS)を募集中です! カジュアル面談もやってます (5月中旬ごろから面談可能です) ぜひお気軽にお問い合わせいただければ幸いです。 求人情報は以下よりご確認ください。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに こんにちは。宮永です。 友人が勉強し始めるというのでノリで受験してきました。申請書取り寄せから免許申請まで一連の体験談を書いていきます。 先に白状すると、8時間勉強で合格したのは私の事前知識が関係しています。勉強面以外の工程が他の資格と比べてかなり特殊だったので、これから受験する人の参考になればと思います。 ※2024年4月から受験申請等の手続きが色々変わったようです。受験会場が追加された・電子申請ができるようになったらしいですが、あくまで私の体験談として読んでいただければと思います。 衛生管理者とは 職場において労働者の健康障害を防止するために、一定の人数以上がいる事業場に対して有資格者を選任しなければならないと定められています。その資格が衛生管理者免許です。 法定の有害業務を扱う事業場も担当できる第一種衛生管理者と、有害業務を除く業務を扱う事業場を担当できる第二種衛生管理者があります。今回は第二種衛生管理者を受験しました。 色々条件はありますが、常時50人以上の労働者がいる事業場には少なくとも1人以上の衛生管理者がいるはずです。思ったより身近な資格です。 受験資格 第一種・第二種共に受験資格が決まっています。 労働衛生の実務に従事した経験 が必要で、必要な年数は学歴によって違います。私は「大卒後、1年以上の実務」という受験資格を利用しました。ほかにも10パターン以上ありますが、どれも実務経験が必要です( HP参照 )。 受験申請時に 労働衛生の実務に従事した経験 があることの証明書を事業者に書いてもらい、それを提出します。つまり自分で適当に経歴があったことにはできません。 労働衛生の実務 にどのような業務が当たるかは 既定の証明書 に書いてあります。該当するか分からない場合は事前に試験センターへ問い合わせると良いそうです。 受験申請書取り寄せ 衛生管理者試験は受験申し込みが 郵送申請のみ で、既定の受験申請書を郵送で取り寄せる必要があります。請求方法の案内に従って取り寄せます。封筒に必要部数や住所のメモ、切手を貼った返送用封筒を入れて送ります。 令和とは思えないアナログスタイル です。 投函から1週間で受験申請書が届きました。冊子になっており、申請書・事業者証明書・受験料払込用紙・受験申請と試験当日の注意事項などがすべてまとまっています。HPに書いていない情報もすべてまとまっているので、受験を完全に決めてない場合でも受験申請書を先に取り寄せてしまう方が良いと思います。 1通のみ取り寄せにすると書き損じや再受験するときに再度取り寄せる必要が出てきます。面倒なので私は2通取り寄せました。 ※ブログ執筆時に協会HPを見たところオンライン申請が追加されたようです。どちらにしろ郵送が必要なようですが、少しは便利になったみたいです。 受験申請(必要証明書の準備) 先述の通り受験資格が決まっており、受験資格を満たすことの証明書を受験申請時に添付する必要があります。私の場合は 大学の卒業証明書 労働衛生の実務に従事した経験についての事業者証明書 を準備しました。私は大学→大学院→就職という経歴で、大学院在籍中にアルバイトをしていたので、大学院在籍中のアルバイト経験で受験資格を満たせます。元アルバイト先に頼み込んで事業者証明書を書いてもらいました。 申請書は冊子に例があるのでそれに従って書きます。受験会場は選択肢がなく、関東在住であれば千葉にある関東安全衛生技術センターです(2024年4月から東京試験場が追加されたようです)。月5~6回ほど試験が行われています。HPに 試験の申し込み状況が見れるページ があるので、先に確認したほうが良いです。 受験料払込用紙の控えと顔写真を受験申請書に貼り、郵送します。 投函から1週間で受験票が届きました。 勉強方法 第二種衛生管理者の試験科目は 労働衛生(有害業務に係るもの以外のもの) 関係法令(有害業務に係るもの以外のもの) 労働生理 の3科目です。各科目40%以上かつ合計60%以上で合格です。 第一種衛生管理者は上記に加えて 労働衛生(有害業務に係るもの) 関係法令(有害業務に係るもの) が追加されます。科目ごとの問題数が若干違いますが、科目の範囲は変わりません。 また、第二種衛生管理者を持っている人が第一種衛生管理者を受験する際は、差分の科目のみ合格すればよいという制度もあります。完全上位互換です。 先述の通り、第一種と第二種は科目数の違いだけなので、テキストは第一種衛生管理者のものを選びました。第二種に比べて第一種のほうがテキストが充実しているためです。 まずは要点がまとまっているテキストを使って試験範囲全体を拾っていきました。範囲は広いですが試験に出るポイントは9割決まっているので、テキストが大事と言っているポイントを確実に覚えていきます。 ある程度知識が付いたら過去問周回に入りました。月に3~4回行われている試験という事もあり、半分以上が過去問から出題されます。なので、過去問をひたすら繰り返して完ぺきにしました。 総勉強時間は約8時間でした。というのも、つい数年前まで塾講師のアルバイトをしており、中学生で習う理科の内容を完ぺきに覚えています。突然中学校の定期テストを渡されても満点を取る自信があります。 出題範囲には中学生の理科で扱うような内容も多く、追加で覚えなくて良い内容が多かったため、短時間の勉強で合格できました。消化酵素の名前や役割・心臓の部屋の名前などを憶えている方は比較的簡単に勉強できるかと思います。 試験会場へのアクセス 試験会場は多くなく、関東在住であれば千葉にある関東安全衛生技術センターに行くことになります。 …この関東安全衛生技術センター、 お世辞にも便利な場所とは言えない 所にあります。東京駅から約1時間ほどの所にあるJR五井駅から、試験日のみ運行している乗り合いバスで20分です。 私は面倒だったので車で行ってしまいました。無料駐車場はありますが受験者に対して少ないです。平日でも 試験開始1時間前には満車 になって止められない人が出てきていました。車で行く際はかなり早めに行くことをおすすめします。 また、会場の周りにはコンビニ等が一切ないです。食べ物飲み物は持参したほうが良いです。 試験 試験は学校の教室を広くしたような部屋でした。いわゆる「小学校みたいな机と椅子」で、荷物を置くスペースが少なかったです。空調はちゃんとしており、暑くも寒くもない環境でした。 一部屋に100人ほどおり、年齢層は30~40代が多く、男女比はバラバラでした。 カンニングについて厳しめで、消しゴムの裏、受験票、電卓に書き込みが無いかしっかりチェックされました。試験開始前と退出時のダブルチェックです。疑わしいものは持って行かないほうが無難です。 試験時間は3時間あるのですが、私は20分で解き終わりました。過去問周回していれば見たことある問題ばかり出てくるので一瞬で終わります。1時間経過すると途中退出できるので、何度も解きなおしをして途中退出します。半分以上の人が1時間経過時点で途中退出するので、時間に追われる試験ではないです。 帰る際に、 免許発行の必要書類を持って帰る必要があります 。受付のような場所に大量に置いてあるので取って帰ります。忘れると郵送で取り寄せる必要があるそうなので、合格した自信がなくても持って帰ったほうが良いです。 結果発表・免許申請 試験から1週間後、HPで合格者の受験番号一覧が掲載されます。その数日後に合格通知書が郵送されます。 試験時に持って帰ってきた免許申請用紙を記入し、収入印紙を貼り、合格通知書の原本を同封して郵送します。 2週間後に免許が届きました。これで晴れて衛生管理者と名乗ることができます。 かかった費用 受験料 8800円 他手数料(免許申請など)2000円 切手代 2192円 テキスト代 3190円 交通費 約5000円 合計 約20000円 母数の大きい試験なので、受験料やテキストは良心的な値段です。証明書取り寄せ等いろんな場面で郵送をつかうので、家の切手がものすごい勢いでなくなりました。 まとめ 特に意味もなく受験した資格の割に準備や申請が大変だったのでブログにしてみました。受験者の多い試験なので、この体験談が誰かの助けになれば幸いです。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
ニフティのN1! Machine Learning Product Engineer 中村です。 最近はRustを書いていて、TerraformとRustの組み合わせでの知見がネット上にないなと思ったので書き残します。 TerraformでLambdaのデプロイを完了させる 業務利用からプライベート開発に至るまで、自分自身はLambdaのデプロイは多くをTerraformで完結させています。 Terraformにアプリケーションコードを含めたくないという声があるのは知っているのですが、Lambdaはちょっとしたコードを書くことも多く、特にプライベートの開発などにおいてはLambda1個をデプロイするためにCI/CDを整備したりするのは大袈裟すぎるなと考えています。そこで、自分自身はLambdaのアプリケーションコードもインフラの一部だと考えて、terraform applyのみでデプロイまで完結させるようにしています。 今回はその手順を記します。 ここからの手順は以下のレポジトリにコードを残していますので、こちらも参照してください。 inakam/cargo-lambda-terraform-sample Contribute to inakam/cargo-lambda-terraform-sample development by creating an account on GitHub. github.com Cargo LambdaでRustコードを準備する Rustのコード管理にはCargo Lambdaを用います。 cargo-lambda/cargo-lambda: Cargo Lambda is a Cargo subcommand to help you work with AWS Lambda. Cargo Lambda is a Cargo subcommand to help you work with AWS Lambda. – cargo-lambda/cargo-lambda github.com LambdaにRustコードをデプロイするためには、クロスコンパイルなどで適切な設定をする必要があるのですが、cargo-lambdaはそれらの設定をやりやすくしてくれます。 今回はTerraformでデプロイするので、実はcargo-lambdaを使う必要性は薄い(自前でRustのコンパイル設定などを調整すればそれでもデプロイ可能)のですが、プロジェクトのセッティングなどでも手間が少なくなるので、cargo-lambdaを使用します。 cargo lambdaのインストール Macの場合は以下でcargo-lambdaをインストールします。 brew tap cargo-lambda/cargo-lambda brew install cargo-lambda そのほかのシステムにおいては、以下を参考にインストールしてください https://www.cargo-lambda.info/guide/installation.html cargo lambda new cargo-lambdaを使ってLambda関数のテンプレートを作成します。 cargo lambda new --http rust-lambda 今回はAWS Lambda Function URLsを使いたいので、HTTP functionを統合するようにしてテンプレートを作成します。(lambda_httpが使える状態でテンプレートが作成されます) ビルドしてみる 作成されたフォルダに移動して、プロジェクトをビルドしてみましょう。 cd rust-lambda cargo lambda build --release --arm64 コードのコンパイルが無事に完了すれば、コードの準備は完了です。 Finished release [optimized] target(s) in 30.26s TerraformでLambdaをデプロイする準備をする 次にTerraformを整備していきます。今回想定しているフォルダ構成は以下のようになります。 . ├── rust-lambda │   ├── Cargo.lock │   ├── Cargo.toml │   ├── src │   │   └── main.rs │   └── target └── terraform ├── lambda.tf ├── lambda_archive ├── modules │   └── lambda_rust_module │   ├── main.tf │   ├── outputs.tf │   └── variables.tf ├── outputs.tf └── variables.tf 先ほどcargo-lambdaで作成したrust-lambdaフォルダと同じ階層にterraformフォルダを作成し、そこから terraform apply を行うことでデプロイできるようにします。 Terraformの設定 それではterraformフォルダ内にファイルを作成し、インフラの設定を行なっていきます。 以下のようにTerraformを設定します。S3 Remote Stateなどが必要であれば適宜設定を追加します。AWSのプロファイルはローカルマシンなどに設定されているプロファイル名を指定してください。 terraform/variables.tf provider "aws" { region = "ap-northeast-1" shared_credentials_files = ["~/.aws/credentials"] profile = "[AWSのプロファイル名]" } terraform { required_version = ">= 1.0.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } variable "name" { default = "rust-lambda" } Lambdaのモジュールを作成する RustでLambdaにデプロイするためのモジュールを作成します。このようにモジュールを作成しておくことで、Lambdaごと分離したい・Lambdaを別の用途で増やしたいという場合に手軽に増やすことができて便利です。 modulesというフォルダの中にlambda_rust_moduleフォルダを作成し、以下の記述をします。 terraform/modules/lambda_rust_module/variables.tf variable "function_name" { description = "The name of the Lambda function." type = string } variable "role" { description = "The ARN of the IAM role to be used by Lambda function." type = string } variable "environment_variables" { description = "A map of environment variables to pass to the Lambda function." type = map(string) default = {} } variable "rust_src_path" { description = "The path to the Lambda function's Rust project." type = string } variable "cargo_lambda_env_name" { description = "name in cargo lambda new [name]" type = string } variable "lambda_zip_local_path" { description = "The path where the Lambda function's zip archive will be saved." type = string } terraform/modules/lambda_rust_module/main.tf output "lambda_function_url" { value = aws_lambda_function_url.this.function_url } terraform/modules/lambda_rust_module/variables.tf # Lambdaの定義 resource "aws_lambda_function" "this" { function_name = var.function_name filename = data.archive_file.this.output_path source_code_hash = data.archive_file.this.output_base64sha256 role = var.role architectures = ["arm64"] handler = "bootstrap" runtime = "provided.al2" timeout = 30 environment { variables = var.environment_variables } } resource "aws_lambda_function_url" "this" { function_name = aws_lambda_function.this.function_name authorization_type = "NONE" } # ローカル環境でcargo-lambdaでビルドするための設定 # ファイルのsha256ハッシュを全て連結させ、連結した文字列からsha512ハッシュを計算し、差分があればビルドする resource "null_resource" "rust_build" { triggers = { code_diff = sha512(join("", [ for file in fileset(var.rust_src_path, "**/*.rs") : filesha256("${var.rust_src_path}/${file}") ])) } provisioner "local-exec" { working_dir = var.rust_src_path command = "cargo lambda build --release --arm64" } } # バイナリをzip化 data "archive_file" "this" { type = "zip" source_file = "${var.rust_src_path}/target/lambda/${var.cargo_lambda_env_name}/bootstrap" output_path = var.lambda_zip_local_path depends_on = [ null_resource.rust_build ] } このTerraformコードの中では、ローカル環境でビルドを行い、バイナリをzip化し、Function URLs付きのLambda関数としてデプロイするという構成が含まれています。 ファイルについてハッシュを計算することで、差分があった時のみデプロイするということを可能にしています。 Lambdaを設定する モジュールが完成したらLambdaを作成します。 terraform/lambda.tf module "api_lambda" { source = "./modules/lambda_rust_module" function_name = "${var.name}-api" role = aws_iam_role.lambda_iam_role.arn rust_src_path = "../rust-lambda" cargo_lambda_env_name = "rust-lambda" lambda_zip_local_path = "../lambda_archive/api.zip" environment_variables = { RUST_BACKTRACE = 1 } } Lambdaに付与するIAM権限も同時に作成します。Lambdaから使うサービスが増えてきたら、ここの権限を追加するといいでしょう。 terraform/iam.tf # lambda用Roleの設定 resource "aws_iam_role" "lambda_iam_role" { name = "${var.name}-iam-role" assume_role_policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "lambda.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } POLICY } # lambda用Policyの作成 resource "aws_iam_role_policy" "lambda_access_policy" { name = "${var.name}-access-policy" role = aws_iam_role.lambda_iam_role.id policy = <<POLICY { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" } ] } POLICY } 実際にデプロイを行う terraformフォルダ内から実際にデプロイしてみましょう。 $ terraform init $ terraform apply 最後に出てくるURLにアクセスして、Hello worldが表示されると完成です。 Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: api_lambda_function_url = "<https://4wrzwdaows4h3z7vcdc2xlodou0wogpb.lambda-url.ap-northeast-1.on.aws/>" WebAPIを作成するためのLambdaテンプレート Rustを使用したLambdaの場合に比較的需要があるのはWebAPIの作成だと思います。しかし、Lambdaを利用した場合にはactix-web, axum, Rocketなどのフレームワークをそのまま適用することができません。また、lambda_httpを使ったサンプルがWeb上に少なく、構築に苦労するケースが多いです。(苦労しました) そこで以下のようにmain.rsを記述することで、Lambda上で比較的扱いやすくWebAPIを構築できるという例を載せておきます。 rust-lambda/main.rs use std::future::Future; use std::pin::Pin; use lambda_http::{Body, lambda_runtime::Error, Request, RequestExt, Response, run, service_fn}; use serde_json::{json, Value}; type LambdaResult = Result<Response<String>, Error>; fn build_response(status_code: u16, message: Value) -> LambdaResult { let response = Response::builder() .status(status_code) .header("Content-Type", "application/json") .body(message.to_string()) .map_err(Box::new)?; Ok(response) } async fn echo_query(event: Request) -> LambdaResult { let params = event.query_string_parameters(); let name = params.first("name").unwrap_or("world"); let message = json!({ "message": format!("Hello, {}!", name) }); build_response(200, message) } async fn echo_body(event: Request) -> LambdaResult { let body = event.body(); let body_str = match body { Body::Text(text) => text, _ => return build_response(400, json!({ "error": "Invalid request body" })), }; let data: Value = serde_json::from_str(body_str)?; let response = json!({ "message": "Received POST request", "data": data }); build_response(200, response) } async fn not_found() -> LambdaResult { build_response(404, json!({ "error": "Not Found" })) } async fn hello_world() -> LambdaResult { build_response(200, json!({ "message": "Hello, world!" })) } fn route_request(event: Request) -> Pin<Box<dyn Future<Output = LambdaResult> + Send>> { Box::pin(async move { match (event.method().as_str(), event.uri().path()) { ("GET", "/hello") => hello_world().await, ("GET", "/echo") => echo_query(event).await, ("POST", "/echo") => echo_body(event).await, _ => not_found().await, } }) } #[tokio::main] async fn main() -> Result<(), Error> { run(service_fn(route_request)).await } rust-lambda/Cargo.toml [package] name = "rust-lambda" version = "0.1.0" edition = "2021" [dependencies] lambda_http = "0.11.1" tokio = { version = "1", features = ["macros"] } serde_json = "1.0.115" serde = { version = "1.0.197", features = ["derive"] } また、axumはlambda_httpと統合して利用できるようなので、こちらを利用してもいいかもしれません。(この記事のレビュー中に社内で教わりました…!) aws-lambda-rust-runtime/examples/http-axum/src/main.rs at main · awslabs/aws-lambda-rust-runtime A Rust runtime for AWS Lambda. Contribute to awslabs/aws-lambda-rust-runtime development by creating an account on GitHub. github.com まとめ 今回はcargo-lambdaとTerraformを用いてRustでLambdaの環境を手軽に構築する方法について解説しました。 勉強がてらRustに触れてみたのですが、AWSサービスとの組み合わせにおいてもRustの速度は非常に高速で、学習コストは高いというデメリットはありますが、高速に動作すると言う何者にも変え難いメリットを得ることができます。(特に従量課金型のLambdaという環境においては高速に動作するというのは金銭的にもメリットがあると言えると思います) みなさんも良きRustライフをお送りください。
アバター
基幹システムグループ N1! オートメーションスペシャリストの南川です。 今回は、Docker Compose v1 ( docker-compose コマンド) が GitHub Actions の Ubuntu と Windows のイメージで使えなくなった件について説明します。 背景 2024年4月3日の朝に、以前紹介したDockerイメージのビルド失敗を通知するGitHub Actionsのワークフローが失敗しているのを確認しました。 GitHub Actions で Docker イメージを定期的にビルドし、失敗したら Slack に通知させる その時の実行結果のログ (一部マスク済み) は以下の通りです。 Run docker-compose build docker-compose build shell: /usr/bin/bash -e {0} env: ACTION_URL: https://github.com/***/***/actions/runs/*** /home/runner/work/_temp/********-****-****-****-************.sh: line 1: docker-compose: command not found Error: Process completed with exit code 127. どうやら、 docker-compose コマンドが見つからないようです。 原因 Ubuntu と Windows イメージから Docker Compose v1 が削除されたのが原因でした。 2024年4月1日から 3~4 日間 かけて削除されるとのことです。 https://github.com/actions/runner-images/issues/9557 ちなみに、この Issue は GitHub ホステッドランナーに プリインストール済みのソフトウェア一覧ページ から見つけました。 For the overall list of included tools for each runner operating system, see the  Available Images  documentation the runner images repository. https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#preinstalled-software 対応 以下のページを参照し、Docker Compose v1 から v2 に移行する必要があります。 https://docs.docker.com/compose/migrate/ 今回のケースでは、 docker-compose build コマンドを docker compose build コマンドに書き換えるだけで動くようになりました。 修正前 - name: define run url run: echo "ACTION_URL=https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> $GITHUB_ENV - name: docker-compose build run: docker-compose build 修正後 - name: define run url run: echo "ACTION_URL=https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" >> $GITHUB_ENV - name: docker compose build run: docker compose build 「 path:.github/workflows AND ("docker-compose build" OR "docker-compose pull") 」で検索すると、修正が必要なワークフローファイルを確認できます。 https://github.com/actions/runner-images/issues/9557#issuecomment-2033320632 https://docs.github.com/ja/search-github/github-code-search/understanding-github-code-search-syntax まとめ 今回の問題は、 Docker Compose v1 が非推奨になってからも docker-compose コマンドを使い続けていたのが原因でした。 今回、すぐに気づけた要因は 以前に作成した定期的にビルドするワークフロー のおかげであり、興味のある方は是非導入してみてください(宣伝) みなさんも EOL に気を付けながら、ツールを使っていきましょう。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
前回の記事では、ニフティのオプションチームで働く社員に、業務内容やチーム環境についてインタビューを行いました。 今回はインタビューの後編をお届けします。前編はこちらの記事をご覧ください。 【インタビュー】ニフティのオプションサービスを開発しているチームに仕事について聞いてみました!【オプション前編】 オプションチームではどんなスキルが伸ばせますか? T.Mさん 幅広くやるのでフルスタックエンジニアを目指せます。中でも好きな分野を伸ばしてくというスタンスなので更に各々の得意分野で尖っていけますね。私はクリーンアーキテクチャーやドメイン駆動設計について外部発信したりもしています。 たけろいど さん Svelteで外部向けのイベントに登壇したり、UI/UX勉強会を主催したりしています。 H.Sさん @nifty ADクリーナーのインフラは私が作りました。インフラのことなら相談に乗ります。あとはいま勉強しているKubernetesについて社内向けの勉強会を開いていますね。 T.Mさん 専門分野を社内外に発信できる人材が育っていってます。あとはチームの体制がスクラムなのでスクラムの知識がつくし、サービスの立上げ>開発>運用>グロース>サービス終了までの全工程を経験できますね。フロントエンド、バックエンド、インフラ、それぞれ得意分野があるエンジニアがいて、どのスキルを伸ばすにしても目標になる人が揃っています。 とーますさん サーバーサイドをPythonで作っているところは珍しいと思いますね。世の中javaが主流なので。Flask、マイクロフレームワークを身に着けていきたい人にはとてもいいと思います。 チームで取り組んでいることを教えてください。 T.Mさん スクラム体制を採用しているのですが、スプリントは1週間で回していて、コミュニケーションの頻度を意識的に上げています。1週間単位でプロダクトオーナーがやりたいことが変わったりするのでしっかりキャッチしたいんです。 とーますさん 前の職場では振り返りの習慣があまりなかったので、レトロスペクティブが新鮮でした。自分たちの仕事効率化にもつながるしニフティの仕事のしやすさにもつながっていると感じます。 今後の展望を教えてください。 T.Mさん 新しいサービスのリリースに向けて開発を進めています。並行して既存製品の改善も行うので、しばらくは2軸で動いていく予定です。 H.Sさん 基盤の刷新もありますね。 T.Mさん この先1年はスケジュールが埋まっています!楽しみです。 皆さんについて教えてください。 休日はどう過ごしていますか? T.Mさん たけろいどさんは料理ですよね! たけろいど さん 料理はかなり好きで、休日に関わらずやっていますね。最近お弁当をつくるようになりました。 H.Sさん えらすぎでしょ…。 たけろいど さん パスタにはまっていて、僕のパスタは世界一うまいです。 T.Mさん レトロスペクティブの時にパスタの写真見せてもらうんですが、マジでうまそうです。 ▲たけろいどさんのパスタ H.Sさん マイクラやパルワールドなんかのゲームもやりますが、最近は勉強してます。いまはKubernetesがアツいです。家にk8sのクラスタがあります。毎日1時間やってるコツコツやってる感じです。 たけろいど さん …僕も毎日料理してます!笑 とーますさん 私はセキュリティソフトが好きなんですが笑 中でもいわゆるエンドポイントセキュリティが好きで。プライベートでセキュリティソフトを全部使って検証してみたり、第三者機関のレポートを読んだりしています。転職先にニフティを選んだのはプロダクトにエンドポイントセキュリティがあり、そこが魅力を感じたからです。 T.Mさん とーますさんにはこれから商品知識をいっぱい身に着けてもらって、外部エンジニアとの交流会にもいってほしいです。 いま楽しみなことはなんですか? たけろいど さん 秒速5センチメートルのリバイバル上映です! H.Sさん CKAを受けるのが楽しみですね。 とーますさん セキュリティソフト以外の趣味のライブ遠征です。あとは会社のテニス部に入っていて、部活での交流が楽しみですね。 T.Mさん 海外出張が控えているので、ローマのゴハンが楽しみです。 今回はニフティのオプション開発チームのインタビューの様子をお届けしました。ニフティでは、さまざまなプロダクトへ挑戦するエンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトよりお気軽にご連絡ください! ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する .is-style-rounded + .has-text-color{ font-size:95%; }
アバター
自己紹介 T.Mさん 新卒5年目で、サブリーダーをやってます。入社時からオプションサービスの開発運用を担当していて、現在は常時安全セキュリティ24や@ nifty セキュア・プライバシーなどのセキュリティサービスを中心に、おしゃべりなども担当しています H.Sさん 同じく新卒5年目で、@nifty ADクリーナーのバックエンド、インフラ中心に開発を行っています。オプションチームには2023年3月に配属されました。前のチームでは課金システムチームの開発を担当していました。勉強が趣味で今は CKA 取得に向けて毎日コツコツ勉強しています。 たけろいどさん こんにちは、たけろいどです。フロントエンドとたけのこの里が好きな新卒3年目です。とくにSvelteというフレームワークが好きで業務・趣味問わずよく使います。好きじゃないものはきのこの山です。対戦ヨロ とーますさん 2023年10月に入社したばかりのキャリア社員です。ニフティに入社する前は金融系のシステム開発に関わっていました。前の職場ではコーディングを外注していましたが今は自らコードを書く機会を得てエンジニアらしい仕事ができる環境になったと思います。またBtoBからBtoCへの転向という面もあり自分にとって大きな変化でした。 ニフティの「オプションサービス」とは? ニフティの「オプションサービス」とはなんですか? T.Mさん 光回線サービスをニフティのメインサービスと捉え、それに付属するサービスなのでオプションサービスと呼ばれています。 常時安全セキュリティ24、@nifty ADクリーナー、@nifty まかせて365などたくさんのサービスがあります。(オプションサービスの紹介ページは こちら )オプションサービスを通じ、お客様に通信回線に留まらない付加価値をお届けしたいという想いでサービスのブラッシュアップに日々取り組んでいます。 ニフティのオプションチームについて教えてください オプションサービスすべてをこのチームで担当しているんですか? T.Mさん このチームではオプションサービスの中でもセキュリティ系のサービスを担当しています。他のチームとしては入会システムチーム、サービスインフラチーム、メールチーム、光電話や回線従属系のものは回線チームがあります。 たけろいど さん オプションに関わっているチーム、改めて聞くと多いですね! T.Mさん オプションサービスチームは、オプションサービスを専業でやっている唯一のチームです。最近は回線とは別にオプションサービスを単体として販売することもあるので、オプションという枠を超えてメインのサービスとして提供していたりもしますね。常時安全セキュリティ24、@nifty ADクリーナーがそれにあたります。 このチームで立上げしたサービスはありますか? T.Mさん @nifty ADクリーナーについては立上げからやりました。 とーますさん 会社の売上にも貢献できている、チームの看板サービスと言えますね。 開発スタイルや環境について教えてください。 たけろいど さん バックエンドはPython(Flask)で、フロントはSvelteかJinja2を使っています。元はphpでしたが思い切って刷新しました。コードについてもリファクタリングが必要だったので、環境変更に伴い書き換えました。 T.Mさん フロントの刷新についてはたけろいど君がグイグイ引っ張ってくれました。 たけろいど さん そう、フロントエンドは自分が引っ張りました。 Jinja2という話が出ていましたが、UI/UXの面でユーザー体験が下がると考えてチームを説得して押し切りました笑。 T.Mさん フレームワーク等々、たけろいど君に質問攻めをしてこれはやってくれそうだなと思って任せました! たけろいど さん まだ2年目だったのですが、フロントエンドに詳しい人が自分しかいなかったので押し切ることができました笑。 ベースはAPIもあったので土壌はとてもよくやりやすかったので先達に感謝です。無事モダンな環境を実現できて良かったです。 どんな運用があるか教えてください。 T.Mさん オプションチームにおける運用とはサーバー保守等のいわゆる運用だけではなく、お客様の困りごとを解消するための改善行動もあります。 たけろいど さん 店頭でお客様からご相談があった場合、開発チームに対応依頼がくることがあります。その場で解決できた時はとても嬉しいですね。 T.Mさん 使い方のサポートであったり、申し込み処理をしたつもりができていなかった場合のリカバーなどですね。お客様と直接お話しすることで改善に繋げられることもあります。 たけろいど さん 運用はどうしても時間がかかってしまうので大変ですね。でも、とてもやりがいがあります。 今のチーム体制になるまでの変遷を教えてください。 T.Mさん 元は企画と開発がワンチームでやっていたんです。自分が1,2年目のころは部署も一緒でした。その後、組織改編によって企画・開発に別れはしましたが、そのころの経緯もあってサービスについての意見交換も活発ですしフィードバックを受ける機会も多いですね。スクラム体制を採用しているのですが、いまも毎朝デイリースクラムで話をしてます。 H.Sさん 直接会話する機会の他に、Slackでも密にやりとりしています。仕様の確認なども気軽にできる関係なのでとてもいいですね。 とーますさん 企画あっての開発なのでつながりを大切にしています。企画の方で契約しているベンダーの方とも関わりを持つように心がけています。 T.Mさん 商品を販売するバイヤーに商品の売り方を提案する場にも、企画と開発一緒になって臨んだりしています。取り扱っているプロダクトのベンダーの拠点があるヨーロッパにいって、現地のエンジニアと意見交換したりもします。 たけろいど さん 近々また海外に行きますよね。次はどこでしたっけ。 T.Mさん ローマです。今回は企画と僕とで行って、現地の開発のエンジニアやセールスと意見交換をする予定です。ちなみに昨年はオランダへいきました。英語はぜんぜんなんですが笑、なんとかなります! たけろいど さん 英語はオンラインゲームでちょっと使うくらいですね笑。 T.Mさん 開発チームの話に戻りますが、実は僕とたけろいど君の二人体制になっちゃった時期があって。そこにH.S君がヒーロー的に他のチームからやってきたんです。 H.Sさん ヒーローかぁ笑。 当時、自分は4年目でキャリアを見直したかったんです。社内公募制度という希望したチームに異動できる仕組みがあるのでそれを利用しました。前にいたチームでは課金システムのバックエンドとインフラ周りを担当しており、4年の中でいろんな経験をさせていただいたので知識はかなりついていたのは確かですね。 とーますさん 異動してきて特に違和感とかはなかったですか? H.Sさん なかったです。全然違うスキルセットが必要でしたが、元のチームは今のチームとの接点もありましたし、言語は同じでPythonだったので全く問題ありませんでした。 とーますさん 私も前職でPythonを使っていたのですぐに馴染めましたね。 たけろいど さん とーますさんって前職ではどんなことをされていたんでしたっけ? とーますさん 金融系です。前職では開発はほぼ委託していたので、エンジニアとしてプロダクトに関わる機会があまりなかったですね。基本的にBtoBになるのですが、BtoBだと納品後のサービスの姿が見えないことが多いんです。ニフティでは内製開発ですし、BtoCなのでプロダクトに対するフィードバックが聞けたりや売上高を追うことができるのでやりがいを感じます。 後編に続きます! 今回はニフティのオプション開発チームのインタビューの様子をお届けしました。続きは後編の記事をご覧ください。 【インタビュー】ニフティのオプションサービス開発エンジニアってどんな人?【オプション後編】 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに  Gemini AI  事前準備  3分でAIアプリを作りましょう  詳しい使い方 DIの導入  試してみよう! はじめに はじめまして。ニフティ株式会社のLinです。 台湾出身のモバイルアプリエンジニアとして、社内で「マイ ニフティ」の Android および iOS 版の開発を担当しております。 2023年末に、GoogleがGemini APIをリリースしたことで、モバイルアプリでもAIサービスとの連携が容易になりました。 今回はGemini AIアプリの作り方をご紹介いたします。  Gemini AI Gemini AIはGoogleが開発したマルチモーダルAIモデルで、2023年12月6日(米国時間)に初リリースされました。 マルチモーダルのため、テキストだけでなく、動画や画像、音声など様々な種類のデータを入力、出力として扱えることができます。 モデルは下記3種類があります: Gemini Ultra 非常に複雑で幅広いタスクに対して最も高いパフォーマンスを提供する、Gemini最大のモデル。 Gemini Pro 強力なパフォーマンスを維持しつつ、コストやレイテンシがバランスよく最適化された、中規模のモデル。 Gemini Nano デバイス上で実行用に設計された、最も効率的なモデル。 低メモリデバイス用にNano-1(18億パラメータ)、高メモリデバイス用にNano-2(32.5億パラメータ)が提供されます。 Gemini AIのモデル種類 2024年現在、 Gemini Pro は1分間に60回のリクエストまで無料です。今回はGemini ProのAPIを使いましょう。  事前準備 APIキー Android Studio Preview Jellyfishから Gemini API Starter が追加されました。  3分でAIアプリを作りましょう Android Studio Preview(Jellyfish)を起動して、 Gemini API Starter を選択します。 プロジェクト情報を入力します。 先ほど作ったGemini APIキーを入力します。 入力したAPIキーは local.properties での apikey に保存され、 BuildConfig を経由して使われています。 ビルドが完了すると、デフォルトアプリが作成されました。 SDKの依存関係は、以下のようにモジュール(アプリレベル)のGradle設定ファイル(例: <project>/<app-module>/build.gradle.kts )に設定されています。 ... dependencies { ... // Gemini AI implementation("com.google.ai.client.generativeai:generativeai:0.1.2") }  詳しい使い方 Gemini AIを呼び出すには、モデルとプロンプトの設置が必要です。 モデルは下記2種類を使えます: gemini-pro:文字のみの入力。 startChat 経由でチャット機能を実装できます。 history でチャット履歴の設置と確認が可能です。 gemini-pro-vision:画像と文字の入力。 画像の入力は必須です。 文字のみの入力に対してはエラーが発生します。 画像のフォーマットはBitmapとなります。 また、temperatureなどのコンフィグ設定と 安全性設定 もできます: // 安全性設定 val harassment = SafetySetting(HarmCategory.HARASSMENT, BlockThreshold.ONLY_HIGH) val hateSpeech = SafetySetting(HarmCategory.HATE_SPEECH, BlockThreshold.MEDIUM_AND_ABOVE) // コンフィグ設定 val config = generationConfig { temperature = 0.99f // 生成するテキストのランダム性を制御 topK = 50 // 生成に使用するトークンの累積確率を制御 topP = 0.99f // 生成に使用するトップkトークンを制御 } プロンプトについては、モデルによって内容が異なります。 gemini-proの場合は以下の例をご参考にしてください: // モデルを設置します val generativeModel = GenerativeModel( // 文字入力のみ modelName = "gemini-pro", apiKey = BuildConfig.apiKey ) val viewModel = GeminiViewModel(generativeModel) // ViewModelでプロンプトを設定します class GeminiViewModel( private val generativeModel: GenerativeModel ) : ViewModel() { private val _uiState: MutableStateFlow<UiState> = MutableStateFlow(UiState.Initial) val uiState: StateFlow<UiState> = _uiState.asStateFlow() fun respond(inputText: String) { _uiState.value = UiState.Loading // プロンプトを設置します // 文字(必須) val prompt = inputText viewModelScope.launch { try { val response = generativeModel.generateContent(prompt) response.text?.let { outputContent -> _uiState.value = UiState.Success(outputContent) } } catch (e: Exception) { _uiState.value = UiState.Error(e.localizedMessage ?: "") } } } } gemini-pro-visionの場合は以下の例をご参考にしてください: // モデルを設置します val generativeModel = GenerativeModel( // 画像+文字入力 modelName = "gemini-pro-vision", apiKey = BuildConfig.apiKey ) val viewModel = GeminiViewModel(generativeModel) // ViewModelでプロンプトを設定します class GeminiViewModel( private val generativeModel: GenerativeModel ) : ViewModel() { private val _uiState: MutableStateFlow<UiState> = MutableStateFlow(UiState.Initial) val uiState: StateFlow<UiState> = _uiState.asStateFlow() fun respond(inputText: String, inputImageList: List<Bitmap>) { _uiState.value = UiState.Loading // プロンプトを設置します val prompt = content { // 画像(必須) inputImageList.forEach { image -> image(image = image) } // 文字 text(text = inputText) } viewModelScope.launch { try { val response = generativeModel.generateContent(prompt) response.text?.let { outputContent -> _uiState.value = UiState.Success(outputContent) } } catch (e: Exception) { _uiState.value = UiState.Error(e.localizedMessage ?: "") } } } } DIの導入 HiltなどのDI(依存性注入)ライブラリも導入したい場合、以下の例をご参考にしてください: // GeminiModule.kt @Module @InstallIn(SingletonComponent::class) object GeminiModule { // 必要によるモデルのコンフィグ設定と安全性設定を追加します private val harassment = SafetySetting(HarmCategory.HARASSMENT, BlockThreshold.ONLY_HIGH) private val hateSpeech = SafetySetting(HarmCategory.HATE_SPEECH, BlockThreshold.MEDIUM_AND_ABOVE) private val config = generationConfig { temperature = 0.95f topK = 50 topP = 0.9f } @Provides @Singleton @GeminiPro fun provideGemini(): GenerativeModel { return GenerativeModel( modelName = "gemini-pro", apiKey = BuildConfig.apiKey, safetySettings = listOf( harassment, hateSpeech ), generationConfig = config ) } @Provides @Singleton @GeminiProVision fun provideGeminiVision(): GenerativeModel { return GenerativeModel( modelName = "gemini-pro-vision", apiKey = BuildConfig.apiKey, safetySettings = listOf( harassment, hateSpeech, SafetySetting(HarmCategory.DANGEROUS_CONTENT, BlockThreshold.ONLY_HIGH), SafetySetting(HarmCategory.SEXUALLY_EXPLICIT, BlockThreshold.MEDIUM_AND_ABOVE) ) ) } @Provides @Singleton fun provideGeminiRepository( @GeminiPro geminiProModel: GenerativeModel, @GeminiProVision geminiProVisionModel: GenerativeModel ): GeminiRepository = GeminiRepositoryImpl(geminiProModel, geminiProVisionModel) @Provides fun provideApplicationContext(application: Application): Context { return application.applicationContext } } @Qualifier @Retention(AnnotationRetention.BINARY) annotation class GeminiPro @Qualifier @Retention(AnnotationRetention.BINARY) annotation class GeminiProVision // GeminiRepository.kt interface GeminiRepository { fun getGeminiProModel(): GenerativeModel fun getGeminiProVisionModel(): GenerativeModel } // GeminiRepositoryImpl.kt class GeminiRepositoryImpl @Inject constructor( @GeminiPro private val geminiProModel: GenerativeModel, @GeminiProVision private val geminiProVisionModel: GenerativeModel ) : GeminiRepository { override fun getGeminiProModel(): GenerativeModel = geminiProModel override fun getGeminiProVisionModel(): GenerativeModel = geminiProVisionModel } // GeminiViewModel.kt @HiltViewModel class GeminiViewModel @Inject constructor( geminiRepository: GeminiRepository ) : ViewModel() { // 文字のみのモデル private val geminiProModel = geminiRepository.getGeminiProModel() private val chat = geminiProModel.startChat(history = emptyList()) // チャット機能 // 画像+文字のモデル private val geminiProVisionModel = geminiRepository.getGeminiProModel() ... } これで、ViewModelで一度GeminiRepositoryを注入すると、2つのモデルを利用できるようになりました。  試してみよう! メッセージ画面を追加すると、以下のようなアプリを作れます。 下記サンプルは自由にクローンして試せます。 サンプル 今後のモバイルアプリでは、ますますAIサービスが導入されます。 ぜひAI系のアプリ作りを体験しましょう! ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに こんにちは、会員システムグループの渡邊です。 社内にNotionが導入されて2年以上立ちました。 最近は何でもNotionで管理すればいいじゃんという気持ちになってきていて、試しにDB設計をNotionで行ってみたのでその感想を書いていきます。 範囲 今回はDB設計におけるエンティティ抽出とデータモデリング、ER図作成までをNotionだけで行います。 エンティティ抽出とモデリング NotionはMermaid記法が使えるため、こちらを使ってエンティティ抽出を行うことが可能です。 しかし、いきなりコードベースで図を作成するのは難易度が高いため、今回はdraw.ioを使ってエンティティ抽出とデータモデリングを行っていきます。 draw.io for Notion という拡張機能を使うことで、SVGファイルをNotionに貼り付けるだけで直接draw.ioの操作を行うことができます。 エンティティ抽出 今回はユーザーが記事投稿をし、そこにコメントを投稿できるサービスを実装することを想定して設計していきます。 データモデリング 次に各テーブルの関係性を紐づけていきます。 これもNotion上で操作できるので楽ですね。 ER図作成 ER図を作る前にテーブル定義をしていきます。 各テーブルの属性情報をNotionのデータベースで管理していきます。 実際に作ったものが以下になります。テーブルでグループ化するとかなり見やすくなりますね。 続いてこちらをMermaid記法を使ってER図化していきます。 ここで一工夫してMermaid記法に合う形に整えていきます。 Mermaidコピー用のビューを作成し、型と論理名をMermaid記法に合わせるための関数を作成します。 今回は桁数を制限しているのでこちらを型と合わせて表示できるようにするため、新規に 型(Mermaid用) というプロパティを作成し、以下の関数を設定します。 if(!empty(prop("桁数")), ((prop("型") + "(") + format(prop("桁数"))) + ")", prop("型")) 続いて論理名をダブルクォーテーションで囲ってエラーが出ないようにするため、新規に 論理名(Mermaid用) というプロパティを作成し、以下の関数を設定します。 ("\"" + prop("論理名")) + "\"" 完成したものが以下になります。 最後に先ほど作成したテーブルの内容をコードブロックに貼り付けていきます。 貼り付けはUSER,POST,COMMENTの中にコピペするだけです。 erDiagram USER { INT comment_id PK "コメントID" INT post_id FK "PostID" TEXT(10) user_id FK "ユーザーID" TIMESTAMP comment_at "コメント日時" TEXT content "内容" } POST { INT post_id PK "PostID" TEXT(10) user_id FK "ユーザーID" TIMESTAMP post_at "投稿日時" TEXT content "内容" TEXT title "タイトル" } COMMENT { TEXT(10) user_id PK "ユーザーID" TIMESTAMP last_login_at "最終ログイン日時" TIMESTAMP created_at "作成日時" TEXT(256) email_address "メールアドレス" TEXT password "パスワード" } USER ||--o{ POST : "Creates" POST ||--o{ COMMENT : "Has" USER ||--o{ COMMENT : "Writes" これで完成です。 テーブル構成が変わる場合は、Notionのデータベースを修正してコードブロックにコピペし直すだけでER図の更新ができるようになるので非常に便利です! 終わりに 今回はNotionでDB設計を行ってみました。 DB設計の肝であるテーブル定義とER図がNotionで作成、管理できるのは非常に便利だなと思いました。 しかし、一つ問題点がありましてmermaidの仕様上、エンティティの位置調整が自動で行われるため、エンティティが多くなれば多くなるほど複雑なER図になってしまいます。そのため、複雑なシステムの場合は図を分けるなどの工夫が必要になってきます。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
 こんにちは。ニフティ株式会社、新卒2年目の小林です。  近日、Javaプロジェクトをインフラから作ることに着手しており、Mavenを使用したパッケージのビルドを行うことになりました。その際に得た便利なPオプションについて共有します。 変更内容  結論から述べますと、以下の2項目により実現可能です。maven 3.5.2 を使用しています。 ビルド時コマンドの追加  &env はpom.xmlで設定したidを入力します。(例:hoge1, local, develop, production) maven clean package -P &env pom.xmlの変更  <profiles><profile>タグにより、各環境(id)ごとに変化する定数について設定することが出来ます(<properties>)。また、<activation>, <activeByDefault>で指定されたprofileを、Pオプションが設定されていなかった場合の初期値に設定することが出来ます。( 公式 ) <profiles> <profile> <id>hoge1</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <fuga1>/webappファイルが存在する相対パス/</fuga1> <fuga2>/あるファイルが存在する相対パス/</fuga2>    ... </properties> </profile> <profile> <id>hoge2</id> ... <profiles>  そののち、<properties>で設定したタグ名を共通した箇所で使用することで、環境ごとの設定を反映させることが出来ます。  例えば、warとしてパッケージ化する際のwebappDirectoryのパス指定( 参考 )を以下のように変数化することが出来ます。 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>~~~</version> <configuration> <webappDirectory>${fuga1}/directory</webappDirectory> </configuration> </plugin> </plugins> </build> 例えば、 -P hoge1 と指定されていれば、 ${fuga1}には hoge1のprofileで設定されたfuga1が指定されます。 ユースケース 様々な環境における構成図とprofileの例を紹介します。含まれているファイルの拡張子は、読み込む形式に従って適切に変更してください。 ただ、<id>タグと、<properties>タグの中身を変更するのみです。 ローカル環境 設定ファイルの中に、Dockerで作成されたネットワーク内のWiremockを使用するコードがあった場合です。 <profile> <id>local</id> <properties> <fuga1>/ローカル環境用の設定ファイルが存在する相対パス/</fuga1> <fuga2>/ローカル環境用のあるファイルが存在する相対パス/</fuga2>    ... </properties> </profile> 開発環境 設定ファイルの中に、開発用の外部APIを使用するコードがあった場合です。 本番環境も構成は同じになります。 <profile> <id>develop</id> <properties> <fuga1>/開発環境用の設定ファイルが存在する相対パス/</fuga1> <fuga2>/開発環境用のあるファイルが存在する相対パス/</fuga2>    ... </properties> </profile> 本番環境(商用) <profile> <id>production</id> <properties> <fuga1>/本番環境用の設定ファイルが存在する相対パス/</fuga1> <fuga2>/本番環境用のあるファイルが存在する相対パス/</fuga2>    ... </properties> </profile> <profiles>まとめ 以上3環境の設定をpom.xmlに記載した際の<profiles>を以下に記述します。 <activeByDefault>が指定されているため、Pオプションを設定しない場合はlocalのprofileが呼び出されます。 <profiles> <profile> <id>local</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <fuga1>/ローカル環境用の設定ファイルが存在する相対パス/</fuga1> <fuga2>/ローカル環境用のあるファイルが存在する相対パス/</fuga2>   </properties> </profile> <profile> <id>develop</id> <properties> <fuga1>/開発環境用の設定ファイルが存在する相対パス/</fuga1> <fuga2>/開発環境用のあるファイルが存在する相対パス/</fuga2>   </properties> </profile> <profile> <id>production</id> <properties> <fuga1>/本番環境用の設定ファイルが存在する相対パス/</fuga1> <fuga2>/本番環境用のあるファイルが存在する相対パス/</fuga2> </properties> </profile> </profiles> 終わりに  今回はMavenのビルド時における、便利なPオプションについてまとめました。コマンドの追加とpom.xmlの変更のみで、環境ごとに読み込むファイルを変更することが出来ました。Javaの勉強をしていくにつれて、他にも便利な機能が存在すると思うので、環境に合わせた最適なオプションを設定出来るように日々研鑽していきます。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
こんにちは! CSSを使っていると、時々うまくスタイルが適用されないことがありますよね。 私もフロントエンドエンジニアとして、日々JavaScriptなどのプログラミング言語で開発を行っていますが、CSSをうまく使いこなせずに困ってしまうことがよくあります。 プログラミング言語には詳しいけれども、HTML/CSSについてはあまり詳しくないまま使ってしまっているエンジニアも少なくないでしょう。 そこで今回は、私のようなエンジニアでも美しいCSSを書きたいと思い、CSSの詳細度について学びました。その学んだ内容を、説明しながら個人的にも良いアウトプットにつなげたいと考え、解説していきます。 詳細度とは? MDNの説明だとこんな感じに書かれています。 詳細度  (Specificity) は、ある要素に最も関連性の高い  CSS 宣言 を決定するためにブラウザーが使用するアルゴリズムで、これによって、その要素に使用するプロパティ値が決定されます。詳細度のアルゴリズムは、 CSS セレクター の重みを計算し、競合する CSS 宣言の中からどのルールを要素に適用するかを決定します。 詳細度 – CSS: カスケーティングスタイルシート | MDN ある要素に対して、競合する複数のCSS宣言があったときに、どのルールを適用するかを決めるために用いられるアルゴリズムのことのようです。 そのアルゴリズムでは、競合するCSS宣言から最も関連性の高いCSS宣言を決め、適用します。 詳細度の計算方法 では、そのアルゴリズムではどうやって詳細度を計算し、関連性の高いCSS宣言を決定しているのでしょう? 基本的にはID, クラス, 要素の3種類のセレクタに対応するID, CLASS, TYPEという3つの分類に分けて計算します。( ID – CLASS – TYPE と記述するようです) セレクタの3つの分類 ID IDセレクタが該当します。 #hoge みたいな感じ。 一致するセレクタに含まれるIDごとに、 1 – 0 – 0 を追加します。 CLASS クラス .fuga や、属性 [type=”radio”] 、擬似クラス :hover などのセレクタが該当します。 それぞれで、 0 – 1 – 0 を追加します。 TYPE 要素 h1 , h2 , p など、擬似要素 ::before などのセレクタが該当します。 それぞれで 0 – 0 – 1 を追加します。 上記の3種類の分類を左から右の順番で比較し、数値が大きいほうのCSS宣言を適用します。 例を示すと以下のようになります。 #myElement input.myClass { color: red; } /* 1-1-1 - ID列が一番大きいのでこのCSS宣言が適用される */ input[type="password"]:required { color: blue; } /* 0-2-1 */ html body main input { color: green; } /* 0-0-4 */ 上記のセレクタが全て同じ input 要素を対象とした場合、IDで一番大きい値を示している color: red; が適用されます。 #myElement { color: yellow; /* 1-0-0 */ } #myApp [id="myElement"] { color: green; /* 1-1-0 - CLASS列がより大きいのでこのCSS宣言が適用される */ } 上記の例では、ID列が同じ値になっています。その場合はCLASS列の優劣で判定します。 CLASS列がより大きい、 color: green; が適用されます。 示してきた2つの例より、ID → CLASS → TYPEの順に数の大小を判定し、詳細度の大きい値のCSS宣言を適用していることがわかります。 3つの例外 擬似クラスの例外 擬似クラスはCLASS列として計算されますが、 :is() , :not() , :has() は詳細度の計算で、擬似クラスとして見なされず、CLASS列として計算されません。ただし、括弧内の引数は詳細度の計算に利用されます。 p { /* 0-0-1 */ } :is(p) { /* 0-0-1 */ } h2:nth-last-of-type(n + 2) { /* 0-1-1 */ } h2:has(~ h2) { /* 0-0-2 */ } div.outer p { /* 0-1-2 */ } div:not(.inner) p { /* 0-1-2 */ } :where() も擬似クラスの例外の一つで、CLASS列として計算されません。 また、 :is() , :not() , :has() とも違い、括弧内の引数も詳細度の計算に利用されません。 :where(#defaultTheme) a { /* 0-0-1 */ color: red; } footer a { /* 0-0-2 */ color: blue; } インラインスタイル HTMLの各要素にはstyle属性を使用してインラインスタイルを追加することができます。 <p style="color: red;">これは赤になります。</p> 通常のCSS宣言を常に上書きすることができるので、最も高い詳細度を持つと考えることができます。 インラインスタイルのみ例外として、 1 – 0 – 0 – 0 というID列よりも高い詳細度を保有すると考えても問題ないです。 !important !important を使用することで、詳細度を無視しインラインスタイルをも凌駕してスタイルを適用することができる、最強のCSS宣言になります。 <p class="yellow" style="color: red;">これは青になります。</p> p { color: blue !important; } .yellow { color: yellow; } MDNにも記述されていますが、 !important を使用して詳細度を上書きすることは 悪しき習慣 です。 !important を使うことでどんなスタイルでも上書きすることができます。 しかし、 !important をさらに上書きすることは難しく、重ねて !important を使うしかなくなります。 CSSのいたる所に !important が付与されてしまい、新しくスタイルを適用しようにもなかなか反映されない。。。保守性の欠片もないCSSになりかねないのです。。。 !important を使用する際は本当に必要なのか確認してから付与する必要があります。 また、なぜ必要なのかはコメントとして残しておきましょう。 セレクタの用途 各セレクタとその詳細度について説明してきましたが、そもそも色々あるセレクタはどう使い分ければいいのでしょうか? idセレクタ、クラスセレクタ、要素セレクタにわけて説明していきます。 要素セレクタ HTML要素名で指定する要素セレクタは指定した要素全てが対象となるためCSSのリセットや初期設定に利用します。 span { background-color: yellow; } p { color: blue; } a { color: red; } クラスセレクタ クラスセレクタはピリオド . でクラスを指定し、クラスが適用されている全ての要素にスタイルを適用します。 クラスセレクタは以下の理由から一番よく使われるセレクタだと個人的には考えています。 作成者自身がスタイルの適用範囲を決めることができる 他のクラスを要素に割り当てることで、上書きも容易 Webサイトを作成していて、何らかのスタイルを適用したい時にまず一番最初に試すのが、クラスセレクタです。 .notebox { border: 4px solid #666; padding: .5em; } .notebox.warning { border-color: orange; font-weight: bold; } .notebox.danger { border-color: red; font-weight: bold; } IDセレクタ # でIDを指定します。IDセレクタはそのidが設定されている要素を選択します。 IDはドキュメントの中で一度しか使用できず、非常に特別なセレクタとなります。 なので前述した詳細度も一番大きい値になります。 IDセレクタに関しては、他のセレクタを上書きすることができます。 しかし、IDセレクタに適用されたスタイルをさらに上書きすることは、より高い詳細度を適用する必要があるので非常に難しくなります。 なので可能ならIDセレクタは使用せずに、クラスセレクタを使用するほうが良さそうです。 どうしてもIDセレクタを使わざるを得ない場合のみ、この方法を使用しますが基本的には使いません。 #one { background-color: yellow; } h1#heading { color: rebeccapurple; } おわりに 今回は詳細度の話でした。 CSSはまだまだ奥深いので、色々勉強していきたいです。 エンジニアは割とプログラミング言語を重視しがちですが、HTMLやCSSも気に掛ける必要があるかなと思います。 「モダンで保守性の高いJavaScriptで書かれたソースコードだけど、CSSは汚い」みたいなことが意外とありますよね。綺麗なCSSを書けるようにしていきたいです! 参考 詳細度 – CSS: カスケーティングスタイルシート | MDN 要素・クラス・IDによるセレクター – ウェブ開発を学ぶ | MDN ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター
はじめに こんにちは。ニフティ株式会社の並木です。 今回は「Amazon API Gateway」と「AWS Lambda」を使ってAPIを作る方法についてご紹介いたします。 背景 以前「 Zapierを使ってRSSフィードの更新をトリガーにしたTwitterへの自動投稿機能を作ってみた 」で紹介した方法で、RSSフィードの更新をトリガーにしたX(旧Twitter)への自動投稿機能を作成したのですが、上記記事の「④Twitterに文章を投稿したい」で紹介した機能が2023年9月に使用できなくなってしまいました。(詳細: https://help.zapier.com/hc/en-us/articles/18657531069965 ) 別の方法で自動投稿機能を復活できないかという話になり、前から使用していた「Zapier」を流用しつつ、新たに「Amazon API Gateway」「AWS Lambda」「X API」を使用して、自動投稿を実現することにしました。 構成図は以下の通りです。 構成図の【1】【2】については、「 Zapierを使ってRSSフィードの更新をトリガーにしたTwitterへの自動投稿機能を作ってみた 」の処理をそのまま流用します。 【5】の手順は「 LambdaでX APIを呼び出してみた 」に記載しています。 【3】【6】については別途記事を執筆予定です。 「【4】API GatewayからLambdaを呼び出す」が今回の説明範囲になります。 「Amazon API Gateway」とは AWSのサービスの一つで、これを利用することで簡単にAPIを作成することができます。 API Gatewayが窓口となり、受け取ったリクエストを他のAWSサービス(今回はLambda)に渡して処理を行います。 APIの作成と同時に、APIキーを作成することもできます。(今回、ZapierからAPI Gatewayを呼び出すような構成にしたのは、APIキーによる認証をしたかったというのも理由の一つです) 「Amazon API Gateway」から「AWS Lambda」を呼び出す Lambda関数は既に作成済みであることを前提として説明します。 Lambda関数の作成方法については、以前投稿した「 LambdaでX APIを呼び出してみた 」を参照してください。 API Gatewayの作成 作成したLambda関数の「関数の概要」の「トリガーを追加」を押下します。 以下の内容でトリガーを追加します。 「API Gateway」を選択 Intent:Create a new API API type:REST API Security:API Key これでAPI Gatewayが作成されました。同時にAPIキーも作成されています。 このAPIキーを知っている人のみが、今回作成するAPIを利用できます。 APIキーは、ZapierからAPIを呼び出す時に使用します。(構成図の【3】で使用します) 作成したAPIキーは、コントロールパネルの左メニューの「APIキー」から確認できます。 メソッドの作成 作成したAPI Gatewayのリソースでメソッドを作成していきます。 「メソッドタイプ:ANY」のメソッドがデフォルトで作成されていますが、こちらは不要なので削除します。 以下の内容でメソッドを作成します。 メソッドタイプ:POST 統合タイプ:Lambda関数 Lambda プロキシ統合:無効 Lambda 関数:作成したLambda関数を選択 「API キーは必須です」にチェックを付ける Lambdaプロキシ統合とは、APIのリクエスト時の様々な情報をLambda関数のeventパラメータに渡してくれる機能です。(詳細: プロキシ統合のための Lambda 関数の入力形式 ) Lambdaプロキシ統合を有効にすると、APIのリクエストパラメータとLambda関数のeventパラメータのキーをマッピングしなくて済むため便利なのですが、「 LambdaでX APIを呼び出してみた 」で実装したLambda関数の修正(eventパラメータから値を取り出す辺りの処理の修正)が必要となってしまうため、今回はLambdaプロキシ統合を無効にして進めたいと思います。 統合リクエストの編集 統合リクエストのマッピングテンプレートを編集していきます。 統合リクエストとは、API Gatewayからのリクエストを受け取り、必要に応じて変換してバックエンド(今回はLambda)に渡すものです。 統合リクエストのマッピングテンプレートを編集することで、APIのリクエストパラメータとLambda関数のeventパラメータのキーをマッピングすることができます。 統合リクエストの「編集」ボタンを押下します。 マッピングテンプレートに以下を入力します。 コンテンツタイプ:application/json テンプレート本文:{“text”: $input.json(‘$.hoge’)} 統合リクエストのマッピングテンプレートに上記を記載することで、JSON形式でAPIのリクエストパラメータを受け取れるようになります。 curlコマンドで表現すると以下のような形です。 $ curl https://aaa/bbb/ccc -X POST -H "Content-Type: application/json" -d '{"hoge":"値"}' 「hoge」で受け取った値が「 LambdaでX APIを呼び出してみた 」のLambdaのコードの39行目の「t ext = event['text'] 」に渡るようなイメージになります。 統合レスポンスの編集 統合レスポンスとは、バックエンド(今回はLambda)からのレスポンスを操作するものです。 今回はLambda関数からのレスポンスをそのままAPIのレスポンスとして使用するため、マッピングテンプレートを削除します。 統合レスポンスの「編集」ボタンを押下します。 マッピングテンプレートの「削除」ボタンを押下します。 統合レスポンスのマッピングテンプレートを削除することで、「 LambdaでX APIを呼び出してみた 」のLambdaのコードの51~60行目のreturnの内容が、APIのレスポンスとしてそのまま返却されるようになります。 APIのデプロイとテスト API Gatewayの設定が完了したので、「APIをデプロイ」ボタンを押下してAPIをデプロイしてみます。(Lambdaについてもデプロイが実施されていることを確認してください) デプロイが完了したら、テストをしてみます。 リクエスト本文にJSON形式でリクエストパラメータを入力し、テストを実行します。 Xに文章が投稿できていれば成功です! おわりに 統合リクエストと統合レスポンスの設定周りがよく分からず苦戦しました。 上記のテスト機能を活用し、設定を少しずつ修正しながらテストを繰り返し実施することで、やりたいことを実現することができました。 ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 カジュアル面談も随時受付中! ニフティに興味をお持ちの方は キャリア登録をぜひお願いいたします! キャリア登録 connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
アバター