TECH PLAY

KINTOテクノロジーズ

KINTOテクノロジーズ の技術ブログ

936

Introductory Remarks and Self-Introduction My name is Ikeda, and I'm in charge of front-end development at KINTO Technologies. Recently, I've been involved in the development and operation of KINTO ONE and the launch of new services and projects. Introductory Remarks Recently, various JS frameworks are emerging, such as React, Vue.js, AngularJS, Preact and Ember. Svelte and Solid are two that have been gaining momentum lately. (And personally, I would like to see Mithril.js grow more. You can find more information about it here: https://mithril.js.org ). With this in mind, I would like to introduce the KINTO corporate site, the KINTO Technologies corporate site, and my impressions of using Svelte —which is also used in other ongoing products— and some code, including a simple SSG. What is the SSG (static site generator) introduced in this article? From a front-end perspective, a request is run every time you access an element, such as API GET to obtain, say, a list of blog articles and then API GET to view an article in detail. What an SSG (static site generator) does is basically create all the relevant API GET content during the build process. As an advantage, in the above example, API communication does not occur when transitioning from the blog list screen to the detailed screen, so the transition is very smooth. There are various other architectures, such as SPA, ISR, and SSR.   What is Svelte? Svelte is a framework with an extremely small build size, and as I'll explain later, reading and writing is extremely easy. Also, JS frameworks are nearly equal to virtual DOM, but Svelte does not include a virtual DOM because things like changes to DOM are also described in Vanilla JS during compiling. It doesn't build a virtual DOM or anything else needed for re-rendering; it simply replaces the real DOM when the state of the DOM changes. Please see below for more details: JS framework is based on the concept of write less code https://svelte.jp/blog/write-less-code Product Introduction KINTO Corporate Site https://corp.kinto-jp.com/ KINTO's corporate site is created in a SvelteKit (SSG) on [S3 + CloudFront] configuration. After coding, when it is merged into a certain branch, the build task is executed via GitHub Actions, reflected in S3, and distributed with CloudFront. *SvelteKit is an application framework that uses Svelte. It's similar to Next.js using React. See here for details: https://kit.svelte.dev/ KINTO Technologies Corporate Site https://www.kinto-technologies.com/ KINTO Technologies corporate site uses Svelte (SPA) on [S3 + CloudFront]. While KINTO's corporate site uses the SSG method, KINTO Technologies' corporate site uses the SPA method. This corporate site did not have much content at the stage when the repository was set up, plus the SvelteKit beta version had not yet been released, so Svelte (SPA) was adopted. However, the amount of content is increasing, which begs the question of whether SG is really enough. With that in mind, we're now eagerly awaiting the change to SvelteKit. What Makes Svelte Different The biggest difference isn't the library, it's the compiler . Whether it's Vue or React, the size of a library file will take up the build size as it is, so build size will inevitably increase. This is a perfect framework for me, because as far as I'm concerned, fast loading speed = justice . And of course, there are plenty of devices and plug-ins that can be used to improve the loading speed and execution speed of other frameworks as well. Where I Got Stuck When Actually Implementing It There were really very few places where I got stuck. A simple increment can be written in a small number of lines, like this: <script> // define cout let count = 0; // onclickで使用する関数 function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button> If there's anything, it's because this is only a beta version and there are still destructive changes being made, so it's necessary to pick up information each time this happens. Svelte's unique syntax is easy to understand, so I think it's difficult to get stuck even if you're seeing it for the first time. There's also another unique syntax called Await blocks . Take a look at the component that fetches each time you click below. You can write 'await' as is in HTML, and reading is a cinch. <script> let promise = getRandomNumber(); async function getRandomNumber() { const URL = "xxx" const res = await fetch(URL); const text = await res.text(); if (res.ok) { return text; } else { throw new Error(text); } } function handleClick() { promise = getRandomNumber(); } </script> {#await promise} <p>...waiting</p> {:then data} <p>{data}</p> {:catch error} <p style="color: red">{error.message}</p> {/await} For readers who think, "Is it really that easy? I'm not convinced." Don't knock it until you've tried it. Why not give it a try for yourself? Try It Out! Practice https://kit.svelte.jp/ We'll reference the SvelteKit official site while we make it. First, let's make a SvelteKit project in the appropriate directory: sh:terminal npm init svelte static-site-sveltekit Next, you'll be given a choice, so select Skeleton project and any other options as you wish. It's convenient that there's a CLI. When selecting, it should generally look something like this: The following is adopted in this article: eslint + JavaScript with JSDoc comments + prettier + Playwright Generating a Static Site This time, I'm going to try the so-called Jamstack, so I'd like to include some sort of communication. I'll try to obtain an article about Svelte from dev.to . *This article does not cover styling, as it increases the volume. First, let's make the page with the list of articles. <script context="module"> export async function load() { let articles try { articles = await fetch(`https://dev.to/api/articles?tag=svelte&per_page=5&page=1`); articles = await articles.json(); console.log(articles) } catch (e) { console.log(e) } return { props: { articles } } } </script> <script> export let articles const PostArticles = articles </script> <svelte:head> <title>Blog</title> </svelte:head> <div> <h1>Svelte devto Articles</h1> {#each PostArticles as article} <div> <header> <h2>{article.title}</h2> <h4>Tags: {article.tags}</h4> </header> <p> {article.description} </p> <a href={`/blog/${article.id}`}>MORE</a> </div> {/each} {#if PostArticles.length === 0} <div>No Articles</div> {/if} </div> With async await , you can obtain the article by fetching the dev.to API, storing it in articles, assigning it to PostArticles and rendering it with Svelte's 'each' syntax. You can export what is written using context="module" . In other words, it can be called even within the same component. Then pass it to the DOM in the next script section and parse it. It's very clear. The good thing about Svelte is that the sections are clear, so it's easy for the writer and very simple to follow. People say that Vue is easy and React is simple , but I think Svelte is both easy and simple . I digress, but let's make a detailed article next. <script context="module"> export async function load({ fetch, params }) { let article try { article = await fetch(`https://dev.to/api/articles/${params.slug}`); article = await article.json(); console.log(article) } catch (e) { console.log(e) } return { props: { article } } } </script> <script> export let article const post = article </script> <svelte:head> <title>{article.title}</title> </svelte:head> <div> <div> <h1>{post.title}</h1> <section> {@html post.body_html} </section> </div> </div> That's it. Params contains various kinds of information, so you just need to get that information, pass it and render it. That's all there is to it. Let's Build! I've written most of the code. So finally, let's build. As it is, there's no instruction to static generate inside svete.config.js . https://kit.svelte.jp/docs/adapters As mentioned in the above, let's use @sveltejs/adapter-static . Let's start by installing it. sh:terminal yarn add @sveltejs/adapter-static@next -D Next, rewrite svelte.config.js. import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: If { // prerender is not entered, an error will occur prerender: { default: true }, adapter: adapter({ pages: 'build', assets: 'build', fallback: null }) } } export default config; Now, yarn build || npm run build The created article was stored in the build directory. Let's see if we can actually obtain the article. yarn preview || npm run preview We had no problems seeing it, right? Now, all you need to do is store the article according to the project, such as S3, hosting service, or rental server. Impressions Once you've tried Svelte out for yourself and seen the code with your own eyes, I'm confident you'll get a sense of what makes it special. You can create an application with less code thanks to Svelte's write less code concept. So, how about it? Did that come across? Although it's still in development as a beta version, I think you can tell this is a JS framework with a lot to offer. Have a good Svelte life, everyone.
アバター
はじめに こんにちは! KINTO テクノロジーズのプラットフォームGでCloud Infrastructure Team(Osaka Tech Lab)に所属している井木です。同じ大阪にいるチームメンバーの若手ホープがCloudFront Functionの障害対応について執筆すると聞いたので、事前知識としてのCloudfrontエッジ関数ついて記載しようと思います! まずは、CloudFrontについて CloudFrontはコンテンツ配信サービス(CDN)であり、コンテンツの配信をよりユーザに近い地点で行うために、世界各地にCDNの拠点を配置してコンテンツのキャッシュを行っております。ユーザ側は一番近いCDNの拠点にアクセスすることにより低レイテンシーでコンテンツへのアクセスが可能になります。また、エッジロケーションには2つあり、ユーザに近いエッジロケーションとオリジン(コンテンツ)に近いリージョンエッジキャッシュ(リージョナルエッジロケーション)が存在します。 エッジ関数とは? エッジ関数とは、そのエッジロケーションでトラフィックを処理しているエッジサーバー上で動く関数のことを指します。 エッジ関数を利用すると、リクエスト時やレスポンス時に関数を実行することができ、弊社においては主に下記の機能を実装して動かしております。 レスポンスのURLをリダイレクト ヘッダーの追加 リクエストパラメータに応じた画像のリサイズ エッジ関数が動作するタイミング エッジ関数が動かせるタイミングは以下の4つになります。 ビューワーリクエスト ビューワーレスポンス オリジンリクエスト オリジンレスポンス ビューワーはすべての通信に対して処理が走るため、共通の処理を行いたい場合に、オリジンはキャッシュがなかった場合に処理が走るため、オリジンに渡したい情報の制御やキャッシュされるデータのリサイズなどキャッシュされるデータを加工したい時などに利用します。 エッジ関数の種類 エッジ関数には2種類あり、CloudFront FunctionとLambda@Edgeが存在します。 CloudFront Function ユーザに近いエッジロケーションで動くエッジ関数です。 レイテンシーの影響を受けやすい CDN のカスタマイズを大規模に実行できます。 シンプルな処理(ヘッダー操作やリダイレクトなど)に向いており、Lambda@Edgeより費用は1/6未満。 ユーザ側の一番近いエッジロケーションで動作するため、ビューワーリクエストとビューワーレスポンスに対応してますが、オリジンリクエスト、オリジンレスポンスには対応できません。 Lambda@Edge オリジン側に近いリージョンエッジキャッシュで動くエッジ関数です。 CloudFront Functionでは対応できない処理がかかる関数や、AWS SDKを含むその他AWSサービスを利用したりファイルシステムへのアクセス等可能です。 Lambda@EdgeはAWS Lambdaの拡張機能でコンソール上での見え方は一緒ですが、ユーザが設定する環境変数が利用できないなどの機能制限があり、注意が必要です。 また、Lambda@Edgeの関数自体はバージニアリージョンに存在しますが、実際は各リージョンエッジキャッシュで動くために、対応するリージョンにレプリカを配置して動作します。そのためLambdaの同時実行数や各サービスへのアクセスリミットなどについては対応するリージョンにて加算されるため注意が必要です。(日本であれば東京リージョン) ビューワーリクエスト、ビューワーレスポンス、オリジンリクエスト、オリジンレスポンスすべてに対応してます。 CLoudFront FUnctionとLambda@Edgeの違い CloudFront Functions Lambda@Edge プログラミング言語 JavaScript Node.js / Python イベントソース ビューワーリクエスト ビューワーレスポンス ビューワーリクエスト ビューワーレスポンス オリジンリクエスト オリジンレスポンス Scale リクエスト数: 毎秒 10,000,000 件以上 リクエスト数: 1 リージョンあたり毎秒 10,000 件まで 関数の持続時間 1ms未満 ビューワー : 5秒 オリジン: 30秒 最大メモリ 2 MB 128 ~ 3,008 MB 関数コードと含まれるライブラリの最大サイズ 10KB ビューワー : 1MB オリジン: 5MB ネットワークアクセス いいえ はい ファイルシステムへのアクセス いいえ はい リクエスト本文へのアクセス いいえ はい 位置情報とデバイスデータへのアクセス はい ビューワーリクエスト : いいえ ビューワーレスポンス: はい オリジンリクエスト : はい オリジンレスポンス: はい 引用: CloudFront Functions と Lambda@Edge の選択 KINTO Technologiesにおけるエッジ関数の使い分け KINTO Technologiesにおいて、まだ十分に使い分けているわけではないですが、大量にアクセスされるような環境でのビューワーリクエスト/ビューワーレスポンスに関してはコスト及び、Lambdaの同時実行数に影響があるためCloudFront Functionを推奨しております。 それ以外に関しては、CloudFront FunctionはLambda@Edgeに比べて制限が厳しいため、日々のAWSコストを減らすより、Lambda@Edgeを利用することで開発や運用にかかるコストを削減する方法を選択しております。 さいごに 今回は、CloudFrontエッジ関数(CloudFront Function、Lambda@Edge)について記載いたしました。エッジ関数は特性を理解して利用することにより、システムの幅が広がりUXの向上になるものだと思っております。ただし、制限が厳しいため間違えるとエラーが発生する原因となり逆の効果となります。 このタイミングで再度理解していただき、開発に役立てていただければと思います。 CloudFront Functionの障害対応の記事も記載されるのでぜひ見てください! また、プラットフォームG(Osaka Tech Lab)で一緒に働いていただけるメンバーを募集してます! KINTOテクノロジーズ株式会社 プラットフォームG採用TOP   wantedly
アバター
はじめに みなさまこんにちは!グローバル開発部兼テックブログ運用チームの森です。 KINTOテクノロジーズは現在東京・名古屋・大阪にそれぞれ拠点があり、東京は室町 (日本橋)・神保町の2つのオフィスに分かれています。私の所属するグローバル開発部は神保町オフィスにて日々働いております💪 今回は神保町オフィスにて2度ほど開催された情報共有会、その名も 神保町ISM (Information Sharing Meeting) の様子をお伝えします 🥳 ※余談ですが、神保町ISM(イズム)と思っていたメンバーもいたようで、それもそれで神保町らしさを作っていくという意味で良いなと思いました。 開催の背景 2022年の6月に神保町オフィスが開所して早1年、所属グループや新入社員も増えて、なんと約100名の大所帯になりました。 しかし、いまだに「他のチーム・グループが何をしているのかわからない」「どんな人がいるのかわからない」といった声をよく聞きます。 そこで、 他の拠点(Osaka tech lab)で行われているような情報共有会 を神保町でも開催してみては?という想いで、テックブログ運営チームで神保町所属のメンバーを中心に、完全なる勢いで企画してみました! 第1回 神保町ISM 2023年6月23日開催!第1回はとりあえず小さく始めてみよう!ということで30分開催で、Agendaも以下のようなシンプルな形式にしました。 Opening (5分) Ask me anything* (20分) Closing : アンケート案内 (5分) Ask Me Anythingとは? Ask Me Anythingとは「○○だけど、何か聞きたいことある?」のような意味で、AMAと略されます。主にソーシャルメディアを中心に、ホストやゲストに対して何でも質問できるような企画です。その名の通り、その人の経歴や、今の仕事、プライベートまで何でも聞くことができます。Webで調べるといろんなところで開催されているのでぜひご参照ください😄 参考: What is AMA? Understanding the Basics of Ask Me Anything AMAを企画に入れたので、質問を受ける人の選定から始めました。第1回は当時のグローバル開発部グループマネージャーにお願いしましたが、都合のつく日が1日しかなかったので、準備から案内、開催までを約2週間で実行に移しました。 突貫で実施した割に、約30名ほどに参加いただき、それなりに良い感想をいただくことができました🎉 以下、参加者の声です。 普段あまり会話する機会のない人と、仕事とは別に経歴や趣味など、様々な内容をお聞きできる場があるのはいいと思いました 資料でもあまり残っていないKINTO黎明期のお話が伺えて大変興味深かった。いろんなメンバーのお話をもっと伺ってみたい。 [第1回 事後アンケート] 次回も参加したいか? 一方で、AMAは一人の人に質問して話を聞くのがメインなので、参加者全員が参加している感はありませんでした。運営チーム内では振り返りを行い、「ワイワイ感」を創出するにはどうしたらよいか?を次回の課題として運営チームで模索しました。 どうやって振り返ってどうやって企画したかという点も、いろいろと工夫があるのですがそれはまた別の機会にご紹介させてください。 第1回の様子 第2回 神保町ISM そんなこんなで企画された第2回神保町ISMは2023年8月25日に1時間に延長して開催しました。 Agendaは以下の通りです。 Opening(5分) Talk with neighbours(25分)🆕❗ Ask Me Anything(25分) Closing(5分) AMAはとても好評だったので据え置きとして、今回はWoven Payment Solution開発Gの亀井さんに登壇をお願いしました。 皆さんに「ワイワイ」参加していただけるように運営チームが考えた企画は Talk with neighbours です。 かっこよく英語で言ってみましたが、4~5人のチームにわかれてフリートークをするコーナーです。 初めて顔を合わせる人達でフリートークするにはネタが必要なので、サイコロ(通称:ころすけ)でトークのネタを提供する方式を採用しました。 サイコロ?🎲 フリートーク?🗣️ そう、平成を生き抜かれた方々には馴染みもあるでしょう、あの番組を参考にさせていただきました。笑 企画中は、チームを分けて「はいどうぞ」で急にお話できるものだろうか?と心配していたのですが、そんな心配は不要なくらい、各チームが自然に話しはじめたのが印象的でした。 トークの話題が尽きた際にはころすけを転がして新しいテーマでお話しました✨ チームにわかれてワイワイガヤガヤ。伝わりますか? AMAは後半30分で行いましたが、Woven Payment Solution開発Gは普段KINTOとは離れて業務をされていることもあり、前回よりも多くの人から質問が出ました。「もっと話を聞きたかった!」などの声もあったのですが、情報共有会での時間は限られているので、ぜひこの場以外でも気軽に交流いただけたらと思います😄✨ [第2回 事後アンケート] 次回も参加したいか? 今回も前回と同じくらいで約30名のメンバーに参加いただき、「部署横断で人と話せてよかった」「同じオフィスで働く人を知ることができるのはとてもいい機会だと思う」「普段かかわりのない社員の方と交流ができ次回も参加できたらなと思いました!」など好評で、「次回も参加したい」の数は前回を上回ることができました✨ まとめ これまで2回開催してきた神保町ISMですが、総じて感じたのはやはり部署をまたいだ交流を皆さん求めているのだな、ということです。一見、業務には関係がなさそうに思える会ですが、こういった場での会話から「そういえばあの人これに詳しかったな、アドバイスもらえるかな」といった風に実業務にも生きてくる気がします。 まずは2回開催しましたが、改善点もたくさんあります。今後も定期的な開催を継続し所属メンバー同士の交流を深めつつ、神保町オフィス全体を活気づけられたら、と考えています 🎉
アバター
はじめまして!人事採用チームで組織開発担当です。 2023年1月に入社後、最初に手がけた全社イベントがとっても心温まる場になったので記事にさせていただくことになりました。記事は2023年2月開催の様子です。 KTC #thanks Days ![KTC #thanks Days](/assets/blog/authors/hr-team/thanks-days/thanks-days.png =400x) 実施したイベント名です。社内ではKINTOテクノロジーズのことをKTCと呼んでいます。 ■イベント概要 ・開催期間:2/13〜2/15 ・場  所:室町、神保町、名古屋、大阪の各拠点 ・内  容:各拠点に設置された「fleeお菓子bar」でプレゼントしたいお菓子をカップに詰め、日頃の感謝を一言添えたthanksカードを添えて社員同士で渡し合いました。 詰め詰めしたお菓子はこんな感じ 可愛い。。。 なぜやったのか? 理由は2つ。「コミュニケーションの活性化」と「カルチャー形成」です。 入社後いろいろな方と話して分かったのですが、役職、役割、年齢、性別問わず、もっとコミュニケーションを取れたら良いのにな〜という声が多くありました。このイベントをきっかけに、日頃言えていなかったり伝えそびれていた感謝を伝え合い、コミュニケーションを深める機会、そして今後の機会に繋げてほしいなと考えました。 ●感謝にフォーカスした理由 どんなコミュニケーション施策にしようかリサーチしたところ、感謝し合う効果の大きさが分かりました。 ・相手への感謝、優しさ、興味が生まれる ・相手への長所や良いところに着目するようになる ・コミュニケーションが活発になる ・生産性も向上する(あるデータでは幸福な気持ちだと+12%、不幸な気持ちだと-10%の生産性になる結果も) ・幸せホルモン「オキシトシン」が出てHappyになる などなど。普通の会話よりも、感謝をし合うコミュニケーションはさらに質が高いことが分かりました。 ●#thanksチャンネルの存在 さらに、KTCにはSlackに「#thanks」という素晴らしいチャンネルが自然発生しており、日々さまざまな「thanks」が投稿されていました。 しかし投稿者は全体の1割程度でしたので、KTC #thanks DAYSをきっかけにチャンネル利用率を高めたいなと。日頃から感謝を伝え合うカルチャー形成のきっかけに、このイベントを繋げていきたいと考えました。 実際の様子 お菓子Barにはたくさんの方が連日集う大盛況!各拠点、追加でお菓子を3度も仕入れるという嬉しい結果となりました!みなさん、選んでいる時から楽しそうだったのが印象的でした。 Slackチャンネルはどうだったかというと・・・ 可愛いpostが連続する中、こんな投稿も・・・ ・・・??? 「り・が・と・う」を#thanksチャンネルにpostをし、残りの「あ」を卒業メンバーに 手渡すして送り出すという何とも素敵な演出も登場しました!素敵! チャンネル促進の結果 実際に利用者は増えたのかというと、 <3日間の成果> #thanks登録者数 117%up #thanks投稿数  119post リアクション総数 2,000over 投稿者数:332%up(1月合計19名▶︎3日間合計 63名) というこちらも素晴らしい結果になりました!! 最後に 全拠点合同で実施をしてみた結果、数字としてもたくさんのリアクションが発生しましたし、3日間の様子から多くの方に喜んでもらえることができたと感じています。 また、KTCは全社員の約25%が海外国籍の方なのですが、感謝に言葉の壁はなく、文字が読めなくてもなぜか気持ちは伝わる、通じ合えるのだな〜と感じました。聞き取ることすらできない言葉もあったけど、感謝されていると分かる場面が何度もあったのですが、その度に「最高な企画をありがとう!」と毎回脳内変換させていただき、私の自己肯定感は爆上がりでした。 <実はこのPOP、全社員の国の言葉でありがとうと書かれています。> 感謝って当たり前に大事だと思っていますが、いざ意識して、言葉に、行動に移すとこんなにも素晴らしく尊いものだと気づく機会になりました。感謝の気持ちは国境を越える。相手の行動に目を向け、感謝の気持ちをもち、伝える。意識せずとも普通にできる、そんなKTCを目指し、組織の力になっていきたいと思います。 こんな素晴らしい企画を入社して担当させてくれて、本当に감사합니다!
アバター
皆様こんにちは、グローバル開発部兼テックブログチームの森です。普段はグローバル開発部にてWebのPdMを務めています。そんな私ですが、2023年9月1日~3日にかけて開催された iOSDC Japan 2023 へ社内の数名と参加してきました! 興味深いセッションの内容は参加していたiOSエンジニアが書いてくれると思うので、私は運営目線での感想をレポートします😎 (#iwillblog プレッシャーを与えますw) https://iosdc.jp/2023/ 参加のきっかけ iOSエンジニアではない私がなぜiOSDCに参加したのか? 実は最近、テックブログチームでは社外向け勉強会の運営をサポートしたり、社内イベントを企画したりしています。 最近ではコーポレートIT主催の KINTOテクノロジーズMeetup! や、DBREチームの DBRE Summit 2023 などの運営をサポートしました。 まだまだ駆け出しのチームなので、企画中やイベント中にも「どうやったら参加者にもっと楽しんでもらえるだろう?」「どうやったらうまくファシリテーションできるだろう?」といった悩みだらけです🤔 そこで、「iOSDCはどうやらめちゃくちゃ盛り上がるらしい」との噂を聞きつけ、カンファレンスの企画案やその盛り上げ術を学ぶべく、3日間に渡って潜入してまいりました!! 2022年のiOSDCレポートは弊社のiOSエンジニア達がレポートしてくれていますのでこちらもぜひご覧ください👍 https://blog.kinto-technologies.com/posts/2022-10-13-iosdc2022_participation_report/ 受付 会場に入ったらまず受付をして、ネームカードをもらいます。 このカードには入退場管理用のQRとNFCが入っています。QRは会場の入退場管理に使い、NFCはアプリで名札をコレクションすることができるのです。 このアイデアいいな。メモメモ📝 https://blog.iosdc.jp/2023/09/01/name-card-nfc-tag-exchange-2023/ 実は私、2日目にまんまとこのカードを忘れてしまったのですがその際も「名札忘れですね~」とスムーズに新しいカードをいただくことができました。こういうところまで考えられているのだな。メモメモ📝 セッション セッションは大きめのお部屋2つ、小さめのお部屋2つの合計4つのブースに分けて開催されていました。それぞれ聞きたいセッションに参加する形です。 🔻 会場全体図: 2階で各トークを拝聴。1階はスポンサーとコミュニケーション用のブース。飲み物等いただけます。 当日のタイムテーブル をイベント前から見ていたのですが、どのトークをどのタイミングでどのお部屋でやってもらうか、めちゃくちゃ調整されたんだろうな、と感動。4つのお部屋同時進行で、セッション内容によって時間も違うし、登壇者が出席できる日も違うだろうに。この調整力は身に着けたいです。 各セッションのタイトルと登壇者名は「世界の果てまでイッテQ!」のナレーションなどで有名な声優の 立木文彦さん が録音で読み上げてくださりました。聞いてる側も登壇者もテンション上がります🥳 オンライン配信 オフライン参加のチケットを購入すると、オンライン視聴もできました。(オンライン視聴のみのチケットもありました。) オンラインはニコニコ生放送です。私は現地参加できなかったセッションをオンラインで視聴していたのですが、驚きなのがラグがほとんどなかったこと!デバイスや環境にもよると思いますが、5秒もないのではないでしょうか?オフライン参加組とリアルタイムでSlack Chatできました。 会場転換中の配信画面では、スポンサーさんのCMはもちろん、準備期間中のスタッフさん達の様子が流れていたのも印象的でした。8月のイベントから、我々もハイブリッド配信を始めたので、次回の休憩中はこういうの配信するとおもしろいかもなぁ、と思って見ておりました。 LTセッション 参加する前から5分のLTが6-7本あるスケジュールが気になってました。 私も普段社内のイベントやミーティングをファシリテーションするので、このAgenda実現するの!?って思っていたのですが、できるんですねぇ~😂 まず、時間通りに登壇を終了させることが一番難しいと思っていました。ここの工夫がすごい…!5分のリミットが近づくと音楽で焦らせて ペンライト を振るように指示が出ます。登壇者を応援しつつ、登壇時間を厳守させるというなんとも良いアイデアでした。あと、振ってる側も楽しい…!(昨年とはまた違った演出だった様子) 登壇者の推しカラーで応援します。 それぞれ5分を滞りなく進めるため、Q&Aの時間は設けずに後で別のブースで話しかけられるような仕組みでした。登壇者の入れ替えは発表資料の準備のみです。この間もちろんオーディエンスは次の登壇者の"推しカラー"を聞いてペンライトの準備をしておきます。それだけだともちろん時間あまるので、ブースの紹介であったり、登壇者の裏話などをお話いただいて、待ち時間を長く感じさせない素晴らしい司会技術でした👏 演出はもちろんなんですが、このLTセッション、コンテンツとしても非常におもしろかったです。5分で必ず切られるのが前提なので、登壇者の方それぞれ工夫されてまとめていらしたのが印象的でした。 おそらく登壇者の中には言いたいことを全部言えずに終了を迎えた方もいらしたと思いますが、登壇者の皆さんさすがのタイムマネジメントスキルで、オーディエンス側としてはあまりそれを感じなかったです👏 プレゼンや登壇を経験されると多分みんな感じると思うんですが、短く自分の言いたいことまとめるのってすっごく難しい😭 自分もおしゃべりなのでつい長いことお話しがちです。その中で起承転結つけて、かつ、少しユーモアも交えながら…なんてめちゃくちゃ良いアウトプットの場ですよね! また、短い時間のプレゼンはタイムマネジメントスキルが磨かれます。残り時間を見ながらその場で「この部分はカット」などを瞬時に判断して、言いたいことを伝えながらまとめられていて、「きっとファシリテーションなんかもお上手なんだろうな」と感じました。 テックブログチームとしては社員のアウトプット力向上も目指しているので、ぜひ社内でもこういう場を取り入れてみたいです😎✨ ペンライト楽しんでいるテックブログメンバー 自分がやりたいこと=みんな楽しいのかも…!? iOSDCに参加してみた単純な感想ですが、総じてエンジニアでなくても参加してすごく意義のあるカンファレンスでした。 もちろん技術周りの詳しいところはわからないですが、非エンジニアでも自社プロダクトをより良いものにしたいという想いは同じです。 運営を学ぶ目的で参加しましたが、PdM視点からも「エンジニアさんってこういうことを考えているんだな」「こういう考えを私達のプロダクトに入れたらもっとよくなるかも!」と感じましたし、本来の目的だった運営目線では、それはもう非常に参考になるカンファレンスでした🤩 懇親会中、幸運にも実行委員長の 長谷川さん とお話する機会がありました。イベント全体の企画であったり、盛り上がるための工夫などをお伺いしたところ 「自分がやりたいことを具現化しているだけなんだよね」 とおっしゃっていました。これ、真理だなと。イベント終わった今も自分にめっちゃ刺さってます。 参加するまでは「どうやったらみんなが楽しんでくれるだろう?」という悩みを抱えていたのですが、「自分がおもしろいと思ったことをやってみたら、意外とみんなも楽しいのでは?」という新しい発想になりました。 もちろんそれが刺さるか刺さらないかはあると思いますが、この新たな視点を得て、今後もいろいろなイベントを企画・運営していきたいという熱が沸き上がりました🔥🔥🔥 KINTOテクノロジーズでは今後も外部向けのイベント開催を企画してまいります。案内は 弊社Connpass などを通じて随時行いますので、ご興味あればぜひご参加ください✨
アバター
こんにちは。 KINTO テクノロジーズの DBRE チーム所属のhoshinoです。 前職ではWeb制作会社でインフラ、バックエンドエンジニアとして働いていましたが、DBに興味がありその中でも DBRE の活動に魅力を感じたため2023年8月からKINTOテクノロジーズ DBRE にジョインさせていただくことになりました。 DBRE(Database Reliability Engineering)チームでは、横断組織としてデータベースに関する課題解決や、組織のアジリティとガバナンスのバランスを取るためのプラットフォーム開発などを行なっております。DBRE は比較的新しい概念で、DBRE という組織がある会社も少なく、あったとしても取り組んでいる内容や考え方が異なるような、発展途上の非常に面白い領域です。 弊社における DBRE の取り組み例としては、あわっち( @_awache )による DBRE ガードレール構想の実現に向けた取り組みについて というテックブログや今年の AWS Summit の登壇内容 、p2sk( @_p2sk_ )による DBRE Summit 2023の登壇内容 を是非ご覧ください。 今回の記事は、2023年8月24日に開催した DBRE Summit 2023をレポートしたいと思います! DBRE Summit 2023 とは DBRE の最新のトピックとプラクティスを学び、また DBRE ネットワーキングを目的としたイベントです。 connpass による事前申し込みではオンラインとオフライン合計で186名の方々が申し込みをしてくださり、当日も多くの方々にご参加いただけました。 登壇者の皆様、参加者の皆様、お忙しい中 DBRE Summit を一緒に盛り上げていただきありがとうございました! DBREを役割ではなく、文化にしたリンケージの取り組み 合同会社 Have Fun Tech 代表社員、株式会社 Linkage CTO、DBRE ユーザー会 DBREJP 共同運営 曽根 壮大(そね たけとも) / そーだい @soudai1025 さん @ speakerdeck DBRE は役割ではなくデータベースを中心とした運用の哲学であり普段のプロダクト開発の営みの中でデータベースをメンテナンスする文化です。 データベースを管理できる英雄が現れてしまうと、逆にその人に依存することになってしまう懸念点も出てきます。 そうならないよう安定したサービス運用のために英雄がいらない平和な世界を目指す必要があり、そのために、会社として風土を作っていく必要があります。 個人の適性や熱意は必要だけどそれだけでは文化にならないため、まずは風土を整えていくことが重要です。 また、設計はデータベースの安全や運用に直結しているため、開発者が DBRE を実践していく文化が必要です。 Database Reliability Engineering は哲学であり、運用のスタイルで職人芸ではなく仕組みで問題を解決していくために、対処ではなく予防をする先に DBRE があります。 始めるに遅いことはない今から始めましょう! <感想> DBRE を実践していく上で自分たちでどうにかしようとするのではなく、周りを巻き込んで行くことがとても重要なことだと実感しました。 DBRE = 哲学であり文化!会社そのものの風土を作っていくために、積極的に横断的なコミュニケーションを取っていきたいです! メルカリのDBREの今とクエリリプレイツールの比較 株式会社メルカリ DBRE 三谷智史 @mita2 さん メルカリの DBRE は設立して1年程、それまではSREがデータベースを担当していました。 メルカリのデータベースの概要としては、元々はMonolith APIとDBだけでしたが現在はMonolith + MicroServiceに分離されました。 DBRE の主な業務としては各マイクロサービ所有のDB支援としてDeveloperのお悩み解決としてDBの各相談を受けたり、生産性を高めるツールの調査を行っています。 MicroService DB支援を始めるに当たって、プロアクティブに活動したいが課題が見えづらい、DBRE チームの認知がされていない等の課題が存在していました。 対応策として Developer Surveyを実施、DBRE にどんなことを期待するかを選択方式でアンケートを実施 DBRE News Letterを半年に一回発行、DBRE チームからの積極的な発信 効果として徐々に会社内で認知されており、依頼が多くなってきています。 その他の DBRE 業務としては、Monolith DBの運用業務としてDBA業務やモダン化への挑戦を行っています。 本番のクエリをミラーするリプレイツールの選定を目的として、調査観点を設定し、調査を行いました。 リプレイツールとは 本番のクエリやトラフィックを別の環境に対して再現するツールで、移行やバージョンアップ時の影響調査に利用します。 比較したツール Percona query Playback ログベースで使用できてシンプルに使いやすいツール MySQL-query-replayer (MQR) MQRは大規模なリプレイにフォーカスされていてツールを作成したtomboさんのロマンを感じるツール <感想> 組織にどのような課題があるかをDeveloper Surveyでの調査や DBRE News Letterなど積極的に DBRE からの発信をしているという印象でした。 また、リプレイツールに関して調査観点やどのように調査を行っているかを聞けてとても参考になりました。 KINTO テクノロジーズでの DBRE 活動のご紹介 KINTO テクノロジーズ DBRE 廣瀬 真輝 @_p2sk_ さん。 @ speakerdeck 会社全体を横断する組織(プラットフォームG)の中に DBRE が所属しています。 DBRE のRoleとしましては、2つに分類を行っています。 Database Business Office 開発組織や各ステークホルダーからの要求に基づいて課題解決を行ったり、DBRE が提供するplatformの活用を推進する役割 Cloud Platform Engineering ガバナンスに準拠しながらクラウドの効果的な利活用を促進するために、DBに関連する標準やplatformを提供する役割 DBRE の活動内容の決め方としましては4本柱を定義し組織の現状を踏まえ具体的な活動を決定しています。 実際の活動 DBクラスタの情報を収集する仕組み DBシークレットローテーション 検証:Aurora zero-ETL integrations with Redshift (preview) KINTOテクノロジーズの DBRE ではDatabaseのReliabilityを向上をさせるためにプラットフォーム構築を行っています。 その手段としてEngineeringで解決する道を選択しました。 Cloudを適切に活用しAgilityを確保しつつDatabaseのSecurityとGovernanceを守る platformへの昇華させ企業全体に展開することでビジネスにいい影響を与え続ける Database Reliability Engineering というアプローチで進めています。 <感想> DBRE の役割を明確に示し、それを元に組織としての仕組みづくりを行うことで、データベースの信頼性向上とビジネスへの貢献を同時に追求していてとても素晴らしいと感じました。 今後 DBRE として定義された4本柱を元により良い仕組みづくりに貢献していきたいと思います! OracleDBを用いたDBREの実現 〜 オイシックス・ラ・大地でやってみた〜 オイシックス・ラ・大地株式会社 DBRE / DBRE ユーザー会 DBREJP 共同運営 原 智子 @tomomo1015 さん @ speakerdeck SRE/DBRE が実現する可視化の中でもコストの可視化があまり触れられていなかったため、会社のインフラ全体のコスト管理を行っています。 アプローチとしては請求書リストからシステムの実態と課題を把握することを行っています。 また、費用対効果を評価することで事業の利益率向上に貢献しています。 全体のインフラ費用のなかでもDBのコスト割合は高い、DBはコストをかけてまでシステムにとって重要なものではあるが、あぐらをかいて放置してはいけません。 DBのコストダウンのために、開発環境等のDBを利用していない日は基本停止や一番コスト対効果があるアプローチを考案しています。 商用DBを利用する上でライセンス形態とその金額を知っていることは DBRE を体現する上でとても重要です。 ライセンスの棚卸しを行い会社が契約しているライセンスの妥当性を把握しましょう。 今と未来をどうすれば信頼性向上ができるか、成長できるか、楽しくできるか、それを考えていくために時間を使いましょう。 コストを可視化することによって色々見えてくるものあるため、是非コストを可視化するところから事業貢献や信頼性向上へのアプローチをしてみてください。 <感想> 普段聞けないコスト面での可視化のお話を聞けてとても面白かったです。 お話にもありましたが、DBはインフラ費用の中でも割合が高くシステムの中でも重要な部分なので可視化し費用対効果を評価するのがとても重要と感じました。 コスト面も含め今後 DBRE として課題解決をしていけたらなと参考になりました。 ANDPADでのテーブル定義変更のレビュー自動化とガイドライン作成の取り組み 株式会社アンドパッド DBRE 福間 雄基 @fkm_y さん @ speakerdeck プロダクトチームがテーブル定義を変更する場合、DBRE がレビューする運用になっており、そのなかでいくつかの課題が発生しました。そのため、スケール可能なレビュー効率化の仕組みを作ることが必要と感じました。 調査として DBRE から開発者へのレビューコメントの分類を行い、対応できそうなものから小さく段階的にリリースしていくことにしました。早期に成果を得ながら進めていくためにこのアプローチを採用しました。 導線作成の自動化 データベース利用規約は既に作成されていましたが、必要になるまであまり読まれていないなかったと仮説を立て必要なタイミングで表示される導線を作成、利用規約への導線を作るコストのみで閲覧数が増加し、レビュー時の指摘頻度が減少しました。 テーブル定義レビューの自動化 機械的にチェックできる項目は自動でレビューしてくれる仕組みを作成、DBRE のレビューコストが削減されました。 仕組みをつくることによってレビュー効率化だけでなくレビューできていなかったプロダクトにも横展開できたため、 DBRE としてテーブル定義レビューの自動化ができました。 <感想> 導線作成やテーブル定義レビューの自動化を行うことで、適切なタイミングで使用することができ、とても効率化されていて素晴らしいと感じました。 このような仕組みを僕自身も今後作っていけたらなと、とても参考になりました。 株式会社アンドパッド DBRE 久保 路 @amamanamam さん @ speakerdeck あらゆるチームのテーブル定義変更の本番実施が一律的でより質が高くなるような行動方針を作った話 課題としてテーブル定義変更時の検証の質がチームで異なるため検証が不十分のままマイグレーション実施されてしまいサービス影響や障害の恐れがあります。 そのためヒアリングを行い原因を分析。検証の質を担保するための明確なガイドラインを作成しました。 作成したガイドラインの概要 本番実行までにやること一覧を作成 PRに載せてほしいことを作成 リリースタイミング検討フローを作成 導入した結果、検証結果の内容が網羅的かつ統一的なものになりました。 <感想> 課題を明確に洗い出し、課題を元に品質向上のためのガイドライン、プロセスを整理することで、 チーム全体の認識が高まり信頼性を高められていて素晴らしいと感じました。 DBRE としてチーム全体がその課題に共感し、協力して対処する動機づけになるようにガイドラインを整理していきたいです。 パネルディスカッション 「DBRE のこれから」 曽根 壮大(そね たけとも) / そーだい @soudai1025 さん 三谷智史 @mita2 さん 原 智子 @tomomo1015 さん DBRE をはじめるにあたってなにから始めたら良いか? ゴール設定を先に決めそれを元に何をすればよいかを決めていくことが良いかもしれないです。 なにが課題なのかを洗い出して、文化をつくっていくことが大事です。 データベースの標準化などが最初にやるテーマとしては良いかもしれないです。 DBRE を実践していくにあたって DBRE ならではのスキルって何がありますか? 組織を横断して活動を行っていくためコミュニケーションスキルが重要です。 プレッシャーがかかるときに前向きに対応できるパーソナリティーが必要です。 信頼を築ける能力が必要です。 キャリアとしての DBRE の魅力はなんですか? DBの専門性を高められます。 DBは移り変わりが激しくないため、一度身につけた知識や経験を長く活用できます。 DBだけではなくアプリケーション等の視野が広がります。 今後みなさんが取り組んでいきたいこと DBRE としてコミュニティー活動を行っていきたいです。 DBRE としての成功体験を増やしていきたいです。 DBRE がなりたい職種になるように願っています。 <感想> DBRE を実践していくうえで必要なスキルがDBの知識だけではないことに少し驚きました。 DBRE としてDBの知識はもちろん必要ですが、組織を横断した文化を作っていくにはコミュニーケーションスキルや前向きに対応できるパーソナリティー文化を作っていくことがとても重要だと実感しました。 僕自身もDBRE がなりたい職種になるよう願っています! まとめ いかがでしたでしょうか? DBRE 自体まだまだ発展途上で導入している企業が少ない取り組みのひとつです。 その中でさまざまな企業の DBRE 活動をDBRE Summitを通して知ることができとても有意義な時間になりました。 僕自身バックエンドエンジニアから DBRE にジョインして間もない為、DBスペシャリストというわけではありませんが、DBへの改善タスクや横断した文化を作っていくエンジニアリングも重要な DBRE 活動であると、DBRE Summitを通して認識することができました。 https://youtube.com/live/C2b93fgn05c
アバター
はじめに こんにちは。KINTO Technologiesのグローバル開発部でフロントエンド開発をしているクリスです。 今日はフロントエンド開発ではなく、業務タスクの自動化について話をしたいと思います。 先月Slack社が発表した 生産性に関するレポート によると、77%の人は日々のルーティンタスクを自動化することで業務がより効率になり、週に約3.6時間が節約されたと話しました。やはり、普段の業務はできるだけ自動化し、本来のやるべきことに集中し、より成果出せるようになるということですね。 そして、いきなり話が変わりますが、弊社は今年7月から勤怠のルールが変わりまして、毎月リモート可能な上限日数が決まっていて、いつリモートするかはチーム内での調整と部内のメンバーに共有する前提であれば基本は個人の自由になります。メンバーの勤怠予定を把握するために、事前に翌週の予定を報告しなければなりません。 そこでグローバル開発部では、Boxというクラウドサービスに置いてある月単位のエクセルシートに、メンバーがそれぞれ翌週の予定を記入し、リーダーが自分のチーム分をとりまとめてSlackでマネージャーに共有するようにしています。 何が問題? 様々な部署内の事情もあって、現状エクセルでまとめて情報を管理することが一番効率的ですが、問題は情報をマネージャーに共有するフローです。グローバル開発部はメンバーが多く、その結果リーダーもそこそこの人数います。リーダー全員毎週わざわざエクセルシートのスクショを撮って共有するのは多少時間取られ、タスクの切り替えにより精神的な負担になります。また、そもそもリーダーがついていないメンバーもいて、そうなると該当メンバーの予定は共有されなくなり、確認するには直接エクセルから見るしかありません。 上記で述べた二つの問題に対して、最低限の工数を使う前提で、自分の中にあるエンジニアの力を使って解決できたらと思いました。 そこで、BoxとSlackが提供しているSDKを利用し、エクセルの情報を落とし込んで、予定情報をSlackにアップする自動化してみました! 開発環境 今回の自動化はNode.jsを使って、以下のライブラリーで実現しました。実際作ったものはTypescriptを使っていますが、この記事ではJavascriptのコードを表示します。また、実装の際は以下のものを利用します。 dotenv Box SDKやSlack SDKを使うと、トークンなどセンシティブな情報を入れないといけないので、dotenvで環境変数にしたいです。 https://github.com/motdotla/dotenv box-node-sdk Boxが提供しているNode.js用のSDKです。 https://github.com/box/box-node-sdk node-slack-sdk Slackが提供しているNode.js用のSDKです。 https://github.com/slackapi/node-slack-sdk node-xlsx エクセルファイルの情報をJSONに変換してくれるライブラリーです。 https://github.com/mgcrea/node-xlsx canvas-table テーブルを画像にするライブラリーです。 https://github.com/el/canvas-table node-canvas canvas-tableがベースとなるライブラリーです。 https://github.com/Automattic/node-canvas/ 実装 それでは実装を順番に説明して行きたいと思います。 Step1: Boxからファイルを取得 まずはBox SDKが使えるようにBoxの管理画面からアプリを作成する必要があります。Boxのデベロッパーコンソール(xxx.app.box.com/developers/console)から新規作成できます。作成後はアプリに対してクライアントIDが発行されますが、アクセストークンの発行が別途必要です。もし自分のワークスペースを所属会社が管理している場合は、大体は会社の管理者から管理者の画面で承認してもらう必要があります。 トークンを取得できたら、アプリの詳細画面からサービスアカウントIDが発行されたかと思います。アクセスしたいフォルダもしくはファイルにこのサービスアカウントに共有しないと、SDKから取得しようとしても、404エラーが返ってきます。 それではコードのほうに移りたいと思います。まずはBoxのSDKをインストールします。 yarn add box-node-sdk そのあとはこのようなコードを書けば、ファイルを指定の場所にダウンロードできます。 ダウンロード処理の説明は 公式ドキュメント にもあります。 import BoxSDK from "box-node-sdk"; // 発行したトークンをここに入れます。 const boxClient = BoxSDK.getBasicClient("トークン情報"); // その後ファイルから情報を取得する処理があるのでasync/awaitを使います await downloadFile(); async function downloadFile() { return new Promise((resolve, reject) => { boxClient.files.getReadStream( // ファイルID "1234567", // クエリパラメーター、例えばファイルの古いバージョンを取得したい場合などに使う // https://developer.box.com/reference/get-files-id-content/ null, // コールバックの関数 function (error, stream) { if (error) { reject(error); } const output = fs.createWriteStream("ファイルの出力パス"); // 書き終わったらPromiseをresolve output.on("finish", resolve); stream.pipe(output); } ); }) } 上記コードを実行し、ファイルが存在していて、かつアクセス権限が正しく付与されていれば、指定したパスにファイルが書き出されるはずです。 Step2: ファイルから必要な情報を取得 続いて、Boxからダウンロードしたファイルから必要な情報を取得したいと思います。 エクセルファイルなので、 node-xlsx を使って、エクセルの情報をパースします。 yarn add node-xlsx import xlsx from "node-xlsx"; const workSheets = xlsx.parse("ダウンロードしたファイルのパス"); console.log(workSheets) // [ // { // name: "シート名", // data: [ // [], // [], // [] // ] // } // ] これだけで、エクセルのシートごとの情報がネストした配列として取れるので、データを加工したり、不要な分を削除したりできます。 Step3: 情報を画像化にする 率直に「なんでこれが必要なの?」と思う方も多いかもしれません。実際私が自動化をやろうとした最初の頃も、まったく想定していませんでした。ただ、必要な情報を取得した後、テーブル情報をどうやって見やすい状態でSlackに投稿するかいくつかの方法で試してみました。例えば、マークダウンでテーブルを作ることですが、Slackはそれをサポートしていないので、実際やってみたら、レイアウトが相当崩れてしまいました。その結果、テーブル情報を画像にすると、メンバーの予定情報がきれいに並ぶようになりました。 画像化にするためには canvas-table を利用します。 import { CanvasTable } from "canvas-table"; import { createCanvas } from "canvas"; // まずは真っ白な画像(Canvas)を作成 const canvas = createCanvas(画像の横幅, 画像の縦幅); // テーブルに関する情報を定義 const tableConfig = { // 列情報 columns: [ { title: "タイトル" } ], // 各セルの情報 data: [ [ { value: "テキスト", } ] ], // オプション情報 options: { borders: { column: { width: 1, color: "#555" }, row: { width: 1, color: "#555" }, table: { width: 1, color: "#555" }, }, title: { text: "タイトル", }, } }; } const ct = new CanvasTable(canvas, tableConfig); await ct.generateTable(); await ct.renderToFile(fileName); これで下のようなテーブル画像が生成されます。 Step4: Slackに投稿する 次は、作った画像をSlackに投稿します。Slackが提供している @slack/web-api の files.upload を利用します。 yarn add @slack/web-api import fs from "fs"; import { WebClient } from "@slack/web-api"; // SlackのOAuth Tokensをセット const slackClient = new WebClient("トークン情報"); const result = await slackClient.files.upload({ channels: "チャンネルID", initial_comment: "付随するコメントテキスト", file: fs.createReadStream("ファイルパス"), }); これでSlackへのアップロードができました! Step5: GitHub Actionで自動実行 上記のステップで、スクリプトはできあがりましたが、まだ自分のローカルで実行する必要があります。あとはこのスクリプトが自動的に実行されたら完璧ですよね〜 弊社では部署関係なく、よくGitHub Actionsを利用しているので、今回もこちらを利用していきたいと思います。 まずはymlファイルを作成します。 name: ワークフローの名前 # 毎週水曜日の日本時間午後1時に実行(UTC時間午前4時で記載) on: schedule: - cron: '0 4 * * 3' jobs: build: runs-on: ubuntu-latest steps: # リポジトリーをチェックアウトする - name: チェックアウト uses: actions/checkout@v3 # Node環境をセットアップ - name: Setup Node.js environment uses: actions/setup-node@v3 with: # 自分にとって適切なNodeバージョンを指定 node-version: '18' # Yarnでライブラリーをインストール - name: yarn install run: yarn install # スクリプトを実行(実行したいjsファイルがindex.jsの場合下記の通り) - name: Run index file run: node index これで、cronで指定した時間になったら自動的に実行されるようになります(多少ずれたりすると思いますが)。 Step Extra: Font変更 このステップを行う必要は全くありませんが、番外編としてやってみました。弊社はトヨタのグループ会社として、トヨタ独自のフォントを利用しています。それを予定表に適用していきたいと思います。 画像作成の際に cavnas というライブラリーを使っていましたが、実はフォントの設定もできます。トヨタフォントは独自のフォントなので、 フォントファイルを用意して、プロジェクトから参照できるようにしないといけません。 // registerFontを追加importします import { registerFont, createCanvas } from "canvas"; // 必ずcreateCanvasの前に置きます registerFont('フォントファイルのパス', { family: 'フォントの名前' }); const canvas = createCanvas(canvasWidth, canvasHeight); // 画像にする際にフォントを利用するように指定 const config = { columns: [ // ... ], data: [ [ // セル情報の定義 { value: "テキスト", fontFamily: "フォントの名前", } ] ] options: { // タイトルの定義 title: { fontFamily: 'フォントの名前', text: "タイトル", }, } }; } const ct = new CanvasTable(canvas, config); // ... うまくいけば、下のようにフォントが適用された画像が作成されます。 終わりに 今回作ったものはまだまだ改善点があるので、時間があるときにリファクタリングしたり、あると嬉しい機能をつけたりしたいと思います。もしあなたの会社も何か業務タスクの自動化を考えていらっしゃるなら、ご参考になればと思います!
アバター
Hi, my name is Tim, and I am a UI/UX Designer from the Global Product Development Group at KINTO Technologies (KTC). Our UI/UX team has a total of four designers and researchers, each with different cultural and academic backgrounds ranging from architecture to business to geology. This diversity allows us to tackle complex design problems with a broad range of methodologies and bring together holistic, feasible solutions. Challenges One of our primary design challenges at KTC revolves around standardizing design practice and establishing a consistent brand language across all KTC products, ranging from mobile applications to marketing websites . To tackle this, we have released a brand portal which lays out a set of design guidelines, including correct uses of typeface, color palette and key visuals. Brand Portal at a glance When it comes to applying the guideline to KTC products, however, it has become clear that a brand portal alone is not sufficient for a number of reasons: A steep learning curve : For non-designers, it is difficult to apply the brand portal to their projects without understanding first-hand the rationale behind each design decision. A lack of collaboration : In the absence of a design assets library, designers are unable to share their work directly with engineers. As a result every project looks different with various engineers taking over, even the work produced by the designer ends up being inconsistent with what has been coded. Increased overheads : When design updates or changes take place, engineers may need to manually rewrite their codebase, risking inconsistencies and adding unnecessary implementation time and cost. These ongoing challenges, along with a common desire to establish a consistent and scalable solution, allow our team to advocate for a company-wide, cross-functional design system . What is a Design System? The people at Invision has put together an easy-to-understand definition of a design system: A collection of reusable components, guided by clear standards, that can be assembled together to build any number of applications. A design system comprises three main, interconnecting elements: User interfaces (ex. buttons, text fields, icons), guided by Visual styles (ex. colors, grids, typography), and demonstrated through Web patterns (ex. header/footer, contact form, call-to-action) Defining a design system, illustrated While there can be overlapping features between a brand portal and a design system within the context of product design, the former focuses exclusively on a product's brand and appearance, whereas the latter goes beyond the visuals and extends into the realm of interaction design, user interface components, design patterns, and usability guidelines. A well-organized design system functions as a single source of truth for design application, ensuring that everyone follows the same design principles and maintains visual and functional consistency across different products or platforms. How is KINTO Design System created? Nothing exists in a vacuum. Rather than building a design system from the ground up, our team has decided to utilize an open source framework already available. Ultimately, we selected Material Design as the foundation of our organization's design language, with Vuetify as the front-end framework, while referencing Human Interface Guidelines for overlooked yet critical design aspects such as accessibility and inclusivity. KINTO Design System methodology and tools This decision turns out to be greatly beneficial to both design and engineering teams because for the first time, all project members can work on common and different parts of the design system concurrently. Using design tools such as Figma we build out components at the same time engineers review, prototype, test, and document our design in code. This methodology unlocks a channel of open, continuous communication and instills cross-functional collaboration that was lacking in past instances. KINTO Design System implementation of mobile view screens Our talented engineering team has written extensively about documenting the design system using Storybook . https://blog.kinto-technologies.com/posts/2022_09_13_storybook/ Results and Next Steps The implementation of the KINTO Design System works seamlessly in design and in code, allowing engineers and product stakeholders alike to consider our team's work more as essential building blocks of KTC products than mere design suggestions. As a result, engineers are more inclined to learn about and apply the design system without the need for troubleshooting just to meet both design and coding specifications. For example, it would require 3 hours to code from scratch a set of steppers, commonly used in user sign-up flow. But with KINTO Design System and Vuetify, it takes less than 10 minutes to complete. 95 vs. 20 lines of code required for a stepper Despite its immediate impact on the product ecosystem, the KINTO Design System remains a work in progress- and for good reasons. As many new user interfaces and web patterns are under consideration, we continuously reiterate our design approach and maintain productive dialogues with cross-functional partners. As such, our team's long-term objective is to improve the useability of both internal and external products in our organization, and it starts with the KINTO Design System. This is the first half of a 2-part series on KINTO Design System. Our next article will dive into a use case of its application in one of KTC products. Please stay tuned! References Design Systems Handbook - DesignBetter Human Interface Guidelines | Apple Developer Documentation Material Design Vuetify — A Material Design Framework for Vue.js Figma: The Collaborative Interface Design Tool Storybook: Frontend workshop for UI development
アバター
はじめに はじめまして、モバイルアプリ開発グループのmy route by KINTO iOSアプリ開発担当している張と保坂です。 モバイルアプリ開発グループでは、通常CI/CDツールとしてGitHub Actionsを採用しています。今回、Bitriseをmy route by KINTO iOSアプリで初めて導入したので、そのお話をさせていただきます。 Bitrise とは Bitrise(ビットライズ)とは、モバイルアプリの自動化ビルド、テスト、デプロイのためのクラウドベースのCI/CD(継続的インテグレーション/継続的デリバリー)サービスです。 Bitriseは、モバイルアプリ開発における効率化を図るために設計されており、iOS、Android、React Native、Flutterなどの主要なモバイルアプリ開発フレームワークに対応しています。 Bitriseの主な機能には、以下のようなものがあります。 ビルドの自動化 リポジトリのコードが更新されると、自動的にビルドがトリガーされます。ビルドの設定はビジュアルなインターフェースを通じて簡単に行うことができます。 テストの自動化 ビルドが完了した後、自動的にテストが実行されます。Bitriseは、さまざまなテストツールとの統合をサポートしており、ユニットテストやUIテストを含むさまざまなテストレベルでの自動化が可能です。 デプロイの自動化 テストがパスした場合、Bitriseは自動的にアプリをデプロイするための手続きを実行します。Bitriseは、App StoreやGoogle Playなどのアプリストアへのデプロイをサポートしています。 インテグレーションの豊富さ Bitriseは、多くのツールやサービスとの統合をサポートしており、GitHub、Bitbucket、Slack、Jira、Firebaseなどと連携することができます。 クラウドベースのサービス Bitriseはクラウドベースのサービスであり、インフラストラクチャの設定や管理をする必要がありません。開発者は、手軽にBitriseの機能を利用することができます。 Bitriseは、モバイルアプリ開発の効率化や品質向上を図るための強力なツールであり、開発者やチームにとって大きな価値を持つCI/CDサービスです。 Bitrise導入経緯 my route by KINTO iOSアプリへのBitrise導入した経緯は2つあります。 CI/CD環境構築前にBitrise社から説明を受ける機会があり、またその時期にチームメンバー全員のPCが IntelからM1にリプレイスが完了したため、同じM1環境でビルドできるBitriseに魅力を感じました。 Intel Mediumマシン(最も低パフォーマンス)のBitriseとGithub Actionsと比較した下記の実験結果から、料金は約3割削減でき、処理時間に関しては約5割短縮することができることが分かりました。 BitriseとGithub Actionsの動作比較実験(※別アプリで実験): 処理時間比較 実験的回数\マシン名 Bitrise (Intel Medium) Github Actions 1 07:48 16:24 2 11:42 16:18 3 06:53 16:09 平均 08:48 16:17 料金比較 Github Actionsの1分あたりのコストは$0.08 Bitriseの1分あたりのコストは$0.106666 Bitriseの計算式: 1クレジット(cr)=経過分(min) ×マシンスペック(2)・・・① $400 / 7,500クレジット=0.05333…($/cr)・・・② ①、②より、0.05333…($/cr)×2(cr/min)で1分あたりのコストは0.106666…($/min) 実験的回数\マシン名 Bitrise (Intel Medium) Github Actions 1 $0.85 $1.36 2 $1.28 $1.36 3 $0.75 $1.36 平均 $0.96 $1.36 my route by KINTOでのBitrise活用 my route by KINTOではBitriseをTeamsプランで契約、1分間に2credits消費するM1 Mediumマシーンを採用しています。 Teamsプランには価格に応じたクレジットの上限が設定されており、それを超過すると追加でコストがかかるため、GitHub Actionsも併用してコストの最適化を目指しました。 Github ActionsではLinuxのコストはmacOSの10分の1 のため、Linux上で動かすことができるステップ(アプリのビルドが不必要)はGithub Actionsを利用し、macOSのみでしか動かすことができないステップ(アプリのビルドが必要)はBitriseを利用しています。 Bitriseのワークフロー my route by KINTOでは主に下記を自動化させています。 ユニットテスト、App Store・TestFlightへのデプロイ、Slackへのビルド結果通知。今現在、develop・releaseブランチへのPushを契機にビルド、平日の朝にスケジュールビルドを採用しています。 所見として、1度のビルドで6〜11分(12〜22credits)くらい時間がかかっています。 GitHub Actionsのワークフロー GitHub Actionsでは静的解析フローを自動化させています。 SwiftLint:Swift用の静的解析ツールで、チームで定めたコード規約に則っていない場合はPR上で自動で指摘します。 SonarQube:静的解析ツールでSwiftLintではカバーすることができない、重複コード等の解析をします。 まとめと今後の展望 今後の展望としては、Bitriseはモバイルアプリ開発のニーズに合わせてさらなる機能の拡充や改善を行っていくと予想されます。例えば、より高度なテストやデプロイのオプション、より柔軟なワークフローの設定、更なるクラウドベースのリソースの拡充などが期待されます。また、開発者コミュニティとの連携や、他のツールとの統合の向上など、よりシームレスな開発体験の提供が期待されます。KINTOテクノロジーズとしても動向に注視しながら、さらなる活用につなげていければと考えています。
アバター
​ こんにちは( º∀º )/ ​ テックブログチーム、予算統括Gの村山です! 今回はKINTOテクノロジーズ初の外部向けイベント『KTC Meet Up!』を開催するにあたっての 運営スタッフ目線での記事を書いていきたいと思います😗 ​ 勉強会の企画~事務局立ち上げに関する記事は企画者の きんちゃん が書いてくれてますこちら✍️ ​ はじめての「KINTOテクノロジーズ MeetUp!」が開催されるまで ![](/assets/blog/authors/uka/ktc-meet-up/kinchan.png =500x) ​ テックブログメンバーはイベント系のフォローを積極的に行っております! テックブログの運営に携わるだけでなく、 みんなを巻き込んで会社を盛り上げていこー!ってチームなのです( ‘-‘ )ง ​ 遡ること6月後半、8月に外部向けイベントやっちゃうよ~!でJoinしました。 メンバー全員が兼務で、みんな別のグループに所属しています。 わたしは通常業務でお金回りの担当をしているため、 必要備品の購入、当日のサポートを行いました。 KINTOテクノロジーズ初の外部向けイベント開催ということで 必要な機材は一式そろえさせてもらいました。 ​ 今回はオフライン開催でしたが、撮影機材も購入させていただきました! これでオンラインやハイブリット開催もできちゃう😳 ​ 新しい挑戦に前向きな弊社、すてき! ! ​ ここからは写真でお送りします ​ テストまで我慢できず届いたその場で手出しちゃう人たち ​ みんなで試行錯誤 ​ あっという間に当日・・! ​ テステス ​ 今回はお試しで社内向けに配信してみることに ​ おおー! ​ Tシャツもつくったよ〜!おそろ~ ​ わくわく ​ はじまりはじまりー! ​ ​ 社外向けイベントの誕生日ということでバースデーメガネを着用 ​ ​ さすが我らのマネージャー! ​ ​ 個性が活きてる! ​ ​ テックブログチームも登壇しました! ​ ​ 登壇おつかれさま~ ​ ​ 座談会 各テーブル賑わっていました! ​ ​ 喋るのに夢中で食べる暇なかった・・By.登壇者 ​ ​ わいわい ​ ​ たのしそうだあ ​ ​ メモ:ビールとハイボールが人気 ​ ぴーす ​ ぐっ ​ ​ ​ ​ そんなこんなであっという間の2時間でしたー! ​ 事務局、運営メンバーによる今回のイベントで「最高だった瞬間」はこちら ​ 協力者が集まったとき 片付けが終わった瞬間。皆が自発的に動いた! 退場時に「最高でした!」「次いつやるんですか!?」ときかれた! 企画で協力者を募った直後、皆さんから色んなアイデアが続々と出てきたこと 当日イベント会場に着いた時のインパクト 当日の座談会。皆めっちゃ夢中でしゃべってる! 相談をもらえた事。そういうのに関わって盛り上がるのが好き!大成功に終わった! カメラの説明等、みんなでOneTeamでなんとかしたこと! Twitter、始まる前からめっちゃ盛り上がってた。一体感あった! 座談会で、参加者の方から色々と質問有り「ウチでもやってみます」と アンケート結果を見た時。運営が楽しんでいる状況が良い! 現地で機材がバッチリ準備できていたこと 受け付けで名札がほとんどなくなった時! 普段専門性も、部署も違う仲間が、協力してイベント成功に繋がったところ。当日運営もスムーズだったし、みんな自分の出来ることを考えて動いていたことが素晴らしかった! ​ ​ 振り返りも意見が飛び交い充実したものでした。 みんなの協力が目に見える素敵なイベントになりました! ​ おわりに ​ 「事務局スタッフ視点でのテックブログ記事」や「登壇者による事例発表の紹介テックブログ記事」も上がる予定なので細かい内容はそちらで😗 てことでわたしからは、『いい写真がたくさん撮れたので載せたいだけの記事』をお送りしました😳 ​ 最後まで読んでくださりありがとうございました!
アバター
Introduction Hello, my name is Rina and I’m involved in Mobility Market development and operation at KINTO Technologies. Usually I work as a front-end engineer implementing websites using Next.js, but I am also one of the members of our Tech Blog team, where I mainly help manage the publishing schedule of all articles. Speaking of our techblog, one year has passed since our first article was released!🎉 This time, in order to celebrate our memorable 1st anniversary, I want to talk about its development after our launch. If you are interested, you can also read our other article to find out how we built this techblog with Next.js.🙌 Adapting InnerSource At KINTO Technologies, we adapted InnerSource when designing and developing our tech blog. What is InnerSource? According to InnerSource Commons, one of the biggest InnerSource communities, InnerSource software remains proprietary to the company, but within it is open for anyone to use it and contribute to it. Reference: InnerSource Commons That means, it is a style of development where any developer within the company could contribute to the project. Practicing The repository of our Tech Blog is open to all developers in the company, where any of us can contribute by implementing new features, fixing bugs, or reviewing new pull requests. In our GitHub Issues, not only all planned new features will be raised there, but anyone who has new ideas that they could not develop on their own can also submit new issues there. Based on what was written inside the issue, those who are willing to contribute can create GitHub Pull requests (PR), or conduct code reviews on other's PR. However, most of the urgent issues would be handled by the techblog team members. Why InnerSource? There are two reasons why we applied InnerSource methodology to our techblog: To utilize this blog as a space for experimenting with new technology To promote collaboration across different teams To utilize this blog as a space for experimenting new technology We wanted our Tech Blog to be a platform for our developers to try out new technologies so that they could grow. This was also cited by the leader of our Tech Blog team, Nakanishi-san in his interview article (conducted in Japanese). We could have used some external CMS provider, yet we decided to build the blog by ourselves . Sometimes, our blog became a platform for us to showcase what we learnt recently, some others, a space to try out features that could not be considered in other products, or also just to simply perform A/B tests. By creating the Tech Blog from scratch, we tried to create a place to encourage our developers to test new things. We hope that any insights, skills and experiences gained by contributing to the Tech Blog could be applied to future products and projects. To promote collaboration across different teams We also wanted the Blog to help break down barriers between teams within our company. Based on our company structure , the vertical communication is strong, but we believe that there are benefits by promoting horizontal communication as well. So how about making new discoveries by sharing what you know with someone from other teams or divisions? It could lead you to having a new business idea or creating a new long-lasting friendship. By strengthening and diversifying our communication methods, we believe developers could grow further, and non-developers may become more interested in development. These are the reasons why we believe InnerSource could help creating a culture where developers learn actively and non-developers to enjoy development. Features we have added until now Among all the features we developed with this practice, I would like to list 5 of them; all of them developed and reviewed by internal developers across different teams. ① RSS ② Social media share button ③ Latest/Related articles ④ Table of contents ⑤ Pagination Summary In this article, I have introduced how we use InnerSource to develop this Tech Blog until now. Soon we are going to fully renew our design and ship our article category feature! Not only we are publishing new articles every week, but we are planning to improve the blog to make it easier for you to enjoy our content. Please look forward to it!🙌 Last but not least, I would like to take this opportunity to express my gratitude to all our readers, and all the members who have been involved in this Tech Blog project. Thank you!!✨
アバター
Hello. My name is Koyama from KINTO Technologies. I work on mobile app development and maintenance. I am an iOS engineer. Last time, I wrote an article focusing on iOS development, titled( Using Combine to Achieve MVVM ). This time, I’ll be focusing on the Agile development practices we use at our development site. In the team I’ve been part of for a while, we follow Agile development with the Scrum framework. I was asked whether I wish to try being a Scrum Master, so I would like to share my experience. *This post is part of an ongoing series exploring Agile. We have faced various challenges and difficulties in our quest to become Agile as an organization. Although there have been failures at times, we have continued to grow steadily. In this series of articles, I would like to introduce some of our actual efforts. Why Did I Take the Scrum Master Training? I had worked with Scrum in previous projects as well, but always in a developer role. I had never acted as a Scrum Master before, so I felt anxious about taking on the role. Then, I learned that there was a Scrum Master training course available, so I decided to take it. This time, I took the Certified Scrum Master training course hosted by Attractor Inc. My Development Team Let me introduce the development team I belong to. Structure and Roles Product Owner Team Leader Backend Development Team Frontend Development Team iOS Development Team ← I am here now Android Development Team QA Designer In mobile app development, it is not uncommon for the frontend development team to split into two teams like this. This time I was going to be a Scrum Master, and the structure was expected to be as follows: Product Owner Backend Development Team Scrum Master (Team Leader) Frontend Development Team Scrum Master ← I was supposed to be 50% responsible iOS Development Team ← I was supposed to be 50% responsible Android Development Team QA Designer Since the team leader had a background in backend development, the plan was for him to act as the Scrum Master for the backend, while I would handle the frontend. In hindsight, it was a rather unusual arrangement. But at the time, I didn’t recognize that—and even if I had, I wasn’t in a position to voice it confidently. Challenges in the Existing Team Development Of course, since we had been proceeding with Agile development up until then, we had many questions and challenges when using Scrum. What should we do if our development team exceeds 10 people? We are carrying out various sprint events by separating them into backend and frontend, but is it OK? The level of understanding of Scrum varies among the team members. And so on… I attended the training hoping to resolve these issues. Taking on the Training! Training Overview The Scrum Master training consisted of a three-day curriculum and was held online every day from 1:00 pm to 6:00 pm. After that, you can take the exam at any time. Upon passing, you can obtain the Certified Scrum Master qualification issued by the Scrum Alliance. The breakdown of the three-day training was as follows: Day 1: Lectures on Agile and Scrum Day 2: Hands-on Scrum practice Day 3: Lecture and hands-on practice on the Scrum Master You can read about Agile and Scrum in books or online, but the lectures explained the concepts in a much clearer and more understandable way. I was able to relearn the principles and mindsets that I had almost forgotten, and I was able to ask the instructors any questions I had, allowing me to solidify my knowledge in a practical format. It was a very meaningful experience. What Changed after the Training Anyway, I gained confidence That’s what it all comes down to. One absolute requirement for a Scrum Master is to provide guidance on Scrum. With what I had learned up to that point, I couldn’t completely clear up my doubts and often found myself thinking, “This is what the book says, but it doesn’t quite match reality.” Even though I understood the principles, I didn’t feel confident enough to share or promote them within my team. Taking the training cleared up all the questions I had, and the reassurance of being taught by a renowned instructor, who has even published books on Scrum, gave me confidence. I was also able to obtain the Certified Scrum Master qualification, which further reinforced my confidence. What I Tried after the Training Gaining the Product Owner’s Understanding During the training, I discovered that my team was using a number of techniques known as anti-patterns. So I began by explaining to the Product Owner what anti-patterns are and why certain practices we were following fit into that category. Since changing everything at once would be difficult, we decided to start with the more straightforward areas. The team agreed to begin by reevaluating how we manage the backlog. Sharing the Training with Team Members Agile development requires the entire team to be on the same page. I think this is a very important aspect of moving forward with Agile. Even if we were to start by changing the way we manage the backlog, we first needed to unify our understanding. To achieve this, I created new documents for internal sharing and held group training sessions the week after the training. I was able to do this just because I gained confidence from the training. From the team members, I received feedback such as "I realized we had been unknowingly doing anti-patterns" and "I'm glad you shared this immediately after the training!" To be honest, creating the documents and conducting the group training was quite a challenge, but I'm glad I did it. Implementing Improvements To improve our approach to backlog management, I outlined the current issues and the desired state, then began applying those changes starting with the next backlog refinement session. All of this was accomplished in just one week after completing the training. The reason we were able to get this far, even though I was still fresh from the training, was thanks to the product owner’s and development team’s understanding. What Happened to Our System? Sharing the training content with the team went smoothly, but that’s when a new issue came up. It turned out that our team leader had to go on parental leave suddenly. Our company has a parental leave system (for men as well, of course), and the whole team wanted to support child-rearing, so we were happy to send him off. However, it seems that I need to support this big project (in terms of number of members) as the Scrum Master . Oh no! So, the restructured system is as follows: Product Owner Scrum Master ← I was supposed to be 50% responsible Backend Development Team Frontend Development Team iOS Development Team ← I was supposed to be 50% responsible Android Development Team QA Designer The reason for the earlier statement, "Looking back now, I feel that we had come up with a strange structure," is that a single Scrum Master is generally sufficient for a team in the first place. Having separate Scrum Masters—one focused on the backend and the other on the frontend—doesn’t seem to align with the original purpose of the Scrum Master role. There’s a concern that this kind of division could result in Scrum Masters being Scrum Masters in name only, functioning more like team leads. Additionally, the initial team split between backend and frontend naturally required cross-team communication, leading to the challenge of needing someone to serve as a bridge. Furthermore, there was a likelihood that the burden on the bridge member may become very heavy, so I believe that consolidating them into one team at this time was actually a good move, considering future developments (see below). What We Want to Do in the Future Our immediate goal is to continue updating our development team, starting with the improvements mentioned above. Once we are able to run Scrum smoothly, we would like to try splitting our team into feature teams. This will solve all the team challenges we originally faced. In the long term, I would also like to work with other Scrum Masters within the company to spread Agile development using Scrum. Once we've helped them reach a point where they can operate smoothly, we can shift our support to other teams. As Scrum adoption grows, we’ll be able to contribute more effectively to building better products. Conclusion This concludes my experience with the Scrum Master training. Anyway, there are many benefits, so if you are reading this and struggling to make Scrum work well, I encourage you to consider taking the training. The training fee is relatively high, which makes it hard for individuals to take it on their own. However, I believe it includes enough value to justify asking your company or manager to cover the cost. Personally, I think the biggest point of Agile development using Scrum is that each team has different practices. Other companies' practices may not necessarily be applicable to your company. Exploring different ways of doing things can be difficult, but it may actually be the most fun part.
アバター
KINTOテクノロジーズの分析グループでデータエンジニアリングチームのチームリーダーを担当している中川です。 最近はゴルフに目覚め球単価を気にする生活になりました。今年の目標はコースデビューすることです! さて本記事ではKINTOの分析基盤をどのように効率よく開発し、サービスローンチに合わせて分析に必要となるデータを提供しているのかというデータエンジニアリングチームの取り組みについてご紹介させて頂きます。 データエンジニアリングチームの目標 データエンジニアリングチームは分析基盤の開発・運用を行っています。 分析基盤とは、社内外各システムのデータを収集・蓄積し、ビジネスに活かせるようデータを提供する役割を担っている縁の下の力持ち的な役割です。そしてサービスイン直後からデータの利活用ができるように下記を目標として掲げています。 「各種のサービス開始にあわせ、分析基盤にデータを集約し即時提供する!」 課題 ただ上記のような役割・目標を掲げる中でKINTO事業の拡大に伴い以下のような課題がでてきました。 限られた開発リソース(数名体制の少数精鋭チームのため) 事業拡大に伴う連携対象システムの増加 連携対象システムの増加に比例し改修の増加 ※ 改修の増加に関しては、「小さく始めて大きく育てる」というアジャイル的な当社ビジネススタイルも影響しています。 解決方法 上記課題を解決するために、ETLとして当社ではAWS Glueを利用していますが、工数削減の観点として、運用面・開発面の2点を中心に改善案を検討し以下のような手段でアプローチしました。 ノーコードを目指した共通化 カラム自動拡張でより速く柔軟性の高い分析基盤へ 当社のAWS分析基盤環境 2点の改善案の前に、当社の分析基盤環境について説明します。 当社の分析基盤は、ETLにAWS Glue、DBにAmazon Athenaを利用しており、最もシンプルなパターンでは下図のような構成です。データをソーステーブルからロードし、生データを時系列でデータレイクに蓄積、利活用のためにデータウェアハウスに格納するという構成になっています。 AWS Glueでデータ連携のためのワークフロー・ジョブを開発するにあたり、当社ではCloudFormationを利用してワークフロー・トリガー・ジョブ・データカタログ・Python・PySpark・SQLなど一連の資材のデプロイを行っています。 そしてデプロイに必要な資材は主に以下となります。 YAMLファイル (ワークフロー・ジョブ・トリガーなどの設定情報) Pythonシェル (ジョブ実行用) SQLファイル (ジョブ実行用) これらの開発作業工数が課題にあげた通りサービスの増加やテーブル数・カラム数に比例して増加し、開発リソースが逼迫し始めました。 そこで先の解決方法に記載した通り主に2点の改善を行うことで問題を解消したため、その手法に関してご紹介させていただきます。 ノーコードを目指した共通化 「ノーコードを目指した共通化」は、進め方としては下記ステップをとりました。 2022年 第1弾: Pythonプログラムの共通化 2023年 第2弾: YAML、SQLファイルの自動生成 第1弾のPythonシェル部分の改善では、これまでは各サービス単位でワークフロー開発を行い、Pythonシェルについてもワークフロー単位で開発・テスト・レビューを行っていたため工数が膨らんでた点に着目しました。微修正しながら各ワークフロー内で再利用していたプログラム部分を共通化することや、データソースの違いなどを吸収し汎用的な作りにすることでプログラムの共通化を進めました。 これにより現在、共通化コードは重点開発・レビューを集中的に行っていますが、各ワークフロー単位でのソースコード開発は不要となり、データソースがAmazon RDSやBigQueryであれば、Amazon Athenaへのデータ型変換なども含めて全て共通化部分で処理が実行できます。 そのため、各サービスのデータ連携を開始したい場合には、設定ファイルに設定のみを記載するだけというノーコードでのデータ連携を可能としています。 第2弾のYAML、SQLファイルの自動生成は、第1弾では必要な部分として残ってしまった設定ファイルやソース側との連携に必要なView定義に関しての改善となります。 こちらはGAS(Google Apps Script)を利用して、設定ファイルであるYAMLやView用のSQLなどを自動生成するように改修しました。このことによりGoogleスプレットシート上に必要なワークフローIDや連携が必要なテーブル名などの最小限の定義を設定するだけで、設定のためのYAMLファイルやView用のSQLファイルが自動で生成されるようにして開発作業の極小化を図っています。 カラム自動拡張でより速く柔軟性の高い分析基盤へ 「カラム自動拡張でより速く柔軟性の高い分析基盤へ」では、改善前にはデータ連携元で定義しているテーブル定義・項目定義を分析基盤側でもYAML上で定義を行っていました。[^1] そのため、初期構築時はデータ連携元での開発側と同じ数だけ項目定義が必要となり、平均して各サービス『20・30テーブル × 20項目 × lakeとdwh』の800〜1,200程度の項目定義が必要な状況でした。 また当社では小さく始めて大きく育てるという理念の基サービスの拡張を常時行っているため、それに伴うバックエンド側のDB更新も頻繁に発生します。この更新作業も以前設定した800〜1,200項目定義の中から注意深く修正箇所を特定し修正していくことが必要となるため、この点も非常に開発工数を圧迫することとなってきました。 そこで考えたのが、データ連携のためにデータ連携元にアクセスしているので、その際に項目の定義情報も一緒に連携し、分析基盤側の項目定義は自動更新を行うという方式です。 せっかくきちんと開発された情報がソース側にあるので、それを活かさない手はない!という考えです。 具体的な実装方法としては下記のような手順でカラム自動拡張を行っています。 glue_client.get_table AWS Glueのデータカタログからテーブル情報を取得 table['Table']['StorageDescriptor']['Columns'] を連携元から取得した項目リスト col_list で置換え glue_client.update_tablet でAWS Glueのデータカタログを更新 def update_schema_in_data_catalog(glue_client: boto3.client, database_name: str, table_name: str, col_list: list) -> None: """ Args: glue_client (boto3.client): Glue client database_name (str): Databse naem table_name (str): Table name col_list (list): Column list of dictionary """ #AWS Glueのデータカタログからテーブル情報を取得 table = glue_client.get_table( DatabaseName = database_name, Name = table_name ) #col_listでColumnsを置換え data = table['Table'] data['StorageDescriptor']['Columns'] = col_list tableInput = { 'Name': table_name, 'Description': data.get('Description', ''), 'Retention': data.get('Retention', None), 'StorageDescriptor': data.get('StorageDescriptor', None), 'PartitionKeys': data.get('PartitionKeys', []), 'TableType': data.get('TableType', ''), 'Parameters': data.get('Parameters', None) } #AWS Glueのデータカタログを更新 glue_client.update_table( DatabaseName = database_name, TableInput = tableInput ) これらに加えて連携元から取得した項目リストを作成する際には、裏でDB毎に異なるデータ型のマッピングなども実施しておりますが、このようにすることでソース側のスキーマ情報で分析基盤の項目定義を生成することができます。 また、このように分析基盤側の項目定義を自動更新となることで気をつけた点としては、知らない間に我々の管理下にある分析基盤のテーブル構造が勝手に変わって行ってしまうという点があげられます。 この点に関しては変更が発生したら"notification"としてSlackに通知が届くような仕組みとしています。こうすることで知らない間にテーブルの構成が変わってしまっていたということを防ぎ、変更を検知し変更内容をソースシステム側に確認した後に必要に応じて後続のシステムにも変更点の連携ができるようにしています。 [^1]:詳細は割愛させていただきますが、AWS Glueの中にはデータカタログを更新してくれるCrawlerもありますがサンプルデータでの更新やエラー解析ができないなどの問題があり当社では利用を見送っています。 最後に いかがでしたでしょうか? 今回は当社分析基盤でのAWS Glueでの『ノーコードを目指した共通化』、『カラム自動拡張でより速く柔軟性の高い分析基盤へ』の2つの手法をご紹介させていただきました。 これら2点を改善することで開発工数を削減をすることに成功し、現在では40テーブルのデータ連携ジョブでも開発工数は1人日程度に抑えられるようになり、目標である「各種のサービス開始にあわせ、分析基盤にデータを集約し即時提供する!」が実現できています。 同じように開発工数を削減されたい方に是非参考にしていただけますと幸いです!!
アバター
こんにちは(こんばんは)、Svelte不定期連載その6です。 過去の記事はこちら SvelteKit + Svelte を1年間くらい使ってみた知見など※SvelteKit メジャーリリース対応済み Svelteと他JSフレームワークとの比較 SvelteでStorybookを使ってみる AstroでSvelte使ってみた SvelteTips 今回はSvelteKitのSSRデプロイについて書いていこうと思います。 モジュールはこちらです。 @sveltejs/adapter-node SSRのデプロイにはadapterが必要で、今回はNodeのadapterを利用します。 https://www.npmjs.com/package/@sveltejs/adapter-node このadapterは公式のGitHubにも載っています。 https://github.com/sveltejs/kit Express Node.jsのウェブフレームワークです。他にはfastifyなどもありますが、どれを使うかは自由です。 https://www.npmjs.com/package/express 環境設定 まずはSvelte内の設定を行います。SvelteKitはデフォルトでSSRになっているので、そこは特に設定する必要がありません。一方デプロイの際にbuildするためにadapterというのを利用する必要があります。 公式サイト にも記載してありますが、対象のsvelteプロジェクトから yarn add -D @sveltejs/adapter-node でインストールし、 svelte.config.js に以下のコードを追加してください。 import adapter from '@sveltejs/adapter-node'; const config = { kit: { adapter: adapter() } }; export default config; これでプロジェクトを yarn build でビルドすると、ビルドしたファイルがデフォルトの出力先である /build に入り、その中に index.js と handler.js というファイルが作成されますが、 もしビルドしたファイルでそのままサーバーを利用したい場合は、 build/index.js を動作させるための、 node build を実行すれば、サーバーが始動されて、動作確認ができます。( node xxxx の xxxx はビルドしたファイルの出力先なので、デフォルトが build です) 次はルートディレクトリにExpressの設定ファイルを入れます。(事前にexpressをインストールしてください) import { handler } from './build/handler.js'; import express from 'express'; const app = express(); // 例えば今回作ったSvelteKitアプリと関係ない、aws用のヘルスチェックパスを作る app.get('/health', (_, res) => { var param = { value: 'success' }; res.header('Content-Type', 'application/json; charset=utf-8'); res.send(param); }); // ビルドで作ったSvelteはこちらでハンドリングされます app.use(handler); app.listen(3000, () => { console.log('listening on port 3000'); }); 上記の設定を完了したら、 node server.js でExpressサーバーを立ち上げ、 http://localhost:3000 でSvelteKitのアプリを確認できます。 アプリをAWSにデプロイしてみる ここからはAWSにデプロイしていきます。AWSでは要件に応じて様々な構成で実現できます。この記事ではEC2のみを利用し最低限ネットからアプリにアクセスできるて手順を紹介します。 実際はセキュリティやパフォーマンスの理由で、Cloudfront/ALB/VPCなどの組み合わせも検討してください。 AWSサービスを利用すると、料金がかかってしまうので、なるべくコストなどを確認し、利用しない時は該当サービスを停止したほうをお勧めします。 EC2 今回作ったSvelteKitアプリをホストするためのクラウドサーバーサービスです。 https://aws.amazon.com/jp/ec2/ EC2インスタンスの作成 まずはEC2のセットアップから始めたいと思います。 EC2のインスタンスを作成するために、EC2の管理画面に入り、右上にある「インスタンスを起動」をクリックします。 そしたら上記のような画面に遷移されますが、下記の項目の設定を行い、「インスタンスを起動」をクリックします。 名前:自分にとってわかりやすい名前をつけます OSイメージ:自分の好みに合わせればいいですが、この記事ではAmazon Linuxを利用し、後ほど利用するコマンドもAmazon Linuxをベースにします インスタントタイプ:t2.micro (それ以外だと有料になります) キーペア:この記事はSSHクライアントでEC2にアクセスするので設定します ネットワーク設定:この記事はSSHクライアントによる接続と最低限ウェブで確認したいのでHTTPアクセスを許可します EC2インスタンスに接続 EC2を作成すると、一覧画面に戻りますが、そこには今回作成されたEC2インスタンスが出てくるはずなので、次はインスタンスに接続し、中で残りの設定を行います。 一覧画面からインスタンスを選択し、「接続」ボタンをクリックします。そうすると、4つの接続方法が選べますが、今回はSSHを利用します。 先ほどインスタンス起動の画面で、キーペアを作成され、ローカルにダウンロードされたと思いますが、画面に書いてある指示通りにそのキーを利用し、接続します。接続が成功したら様々なものをインストールします。 Node.jsのセットアップ まずはNode.jsをインストールしますが、Node Version Manager(nvm)からインストールしたほうが様々なnodejsバージョンをインストールでき、切り替えも便利です。 https://github.com/nvm-sh/nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash インストール後はターミナルからメッセージが来ると思いますが、 nvm コマンドが実行できるようにパスを通す必要があるので、 以下のコードをコピーして、コマンドラインに貼り付けて実行します。 export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" // nvmコマンドが動くかどうかの確認 nvm --version -> 0.34.0 そしてnvmでnodejsをインストール // 18系のnode.jsをインストール nvm install 18 // インストールしたnodeとnpmバージョンを確認 node -v -> 18.16.1 npm -v -> 9.5.1 // yarnをインストール(npmを利用したい方はこれをスキップ) npm install -g yarn yarn -v -> 1.22.19 これでNode.js周りのセットアップは完了しました。 SvelteKitアプリの配置 次は作ったアプリをインスタンスの中に入れたいと思います。自分のローカルからEC2に移す方法もありますが、今回はGitHubに上げているリポジトリからcloneします。 まずはcloneできるようにGitHub CLIをインストールします。Linuxでのインストール方法は 公式ドキュメント にも記載されています。 // 公式ドキュメントに記載されたコマンド(変わる可能性があるため、必ず公式ドキュメントも確認してください) type -p yum-config-manager >/dev/null || sudo yum install yum-utils sudo yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo sudo yum install gh // バージョン確認 gh --version -> 2.31.0 次は自分のアカウントでログインし、リポジトリをcloneします。 // GitHubへのログイン gh auth login // cloneしたいリポジトリのurlを入れてください gh repo clone https://github.com/xxxxxx/yyyyyyy これでアプリは無事インスタンスにcloneできました。 Nginxのセットアップ 続いてははNginxサーバーをインストールし、configファイルを修正します。 // インストール sudo yum install nginx // nginxフォルダーに移動 cd /etc/nginx // vimでnginxのconfigファイルを開く sudo vim nginx.conf configファイルに server という枠がありますが、 以下のようにプロキシパスを設定します。 この構文はnginxに対して / にアクセスした場合、EC2内で立ち上げたsveltekitサーバーにアクセスするように指示するものです。 server { location / { proxy_pass http://localhost:3000; } } Nodeサーバーの立ち上げとWebからのアクセス 最後はローカルでの確認と同じように、ビルドして、nodeサーバーを立ち上げます。 yarn install yarn build node server.js そして作ったEC2インスタンスが提供しているDNS名からアクセスしてみます。(情報はEC2の一覧ページから確認できます) 以下のように表示ができました! しかし、これでEC2インスタンスとの接続を中止すると、Nodeサーバーも停止になってしまいます。 そこで pm2 というライブラリーを使ってNodeサーバーの永続化にします。 https://pm2.io/docs/runtime/guide/installation/ yarn global add pm2 pm2 -v -> 5.3.0 pm2 start server.js // 今pm2で立ち上げているnodeサーバーの状況と停止したいサーバーidを確認 pm2 status // 今pm2で立ち上げているnodeサーバーを停止します pm2 stop [id] これでEC2との接続を止めても、引き続きウェブから閲覧できます! 以上、SvelteKitのSSRアプリをAWSにデプロイする方法でした。
アバター
Introduction Handling variables like URLs, dates, or times in translatable sentences is a common challenge in localization. The position or format of these can vary significantly depending on the language, making it tricky to manage strings across languages, especially if you are managing multiple languages at the same time. Hello! I’m Maya and I lead localization at KINTO Technologies. This article is for anyone managing multilingual content in-house in several languages without a coding background but would like to create a lean mechanism alongside their development colleagues. Don't worry if you're not familiar with coding; while having direct access to colleagues who can assist with implementation would be ideal, there are many resources available online to support you in managing your source language and translation data cohesively with your engineering team. In our case, we use Lokalise as our TMS and source of truth, integrated with GitHub to keep translation data and development in sync. Why This Matters For localization specialists, understanding how to use dynamic placeholders like URLs or dates can streamline your workflow and close the gap in communication between dev and localization teams. Shoutout to our teammate Wen-san for explaining this method in a beginner-friendly way! In my case, since we implemented this approach, we’ve significantly reduced manual string adjustments, especially for placeholder-heavy sentences like terms and conditions and date/time formats. Internal rework due to these issues dropped from about 5 reports per month to nearly zero. We not only saved time but also minimized QA back-and-forth and improved trust between localization and development teams. The Problem Let’s take a sample sentence in English with two URLs: "By using our service, you agree to our Terms and Conditions and Privacy Policy ." In Japanese, the URLs will be placed in a different position relative to the English one: "本サービスを利用することにより、 利用規約 および プライバシーポリシー に同意したものとみなされます。 If we had a third language, especially the more it differs from the above two, the position may again be different. If you’re handling multiple languages, managing these differences manually can be very time-consuming and error-prone. The Solution: formatString() Here’s where this function can be a lifesaver. It allows us to create a flexible template where the URLs can be placed dynamically, depending on the language. You can adjust the placement of the URLs in a sentence without having to rewrite the entire sentence for each language. The tedious alternative many teams still face is hardcoding the URLs directly into each localized string, which leads to manually having to recreate near-identical sentence variants with cluttered string files, a higher risk of typos or broken links, and painful maintenance when URLs change. This article introduces one solution to this issue using JavaScript as an example, but it can also be expressed in other programming languages such as Phython, C# or PHP. Step 1: Identify the Dynamic Elements in Your Sentence Let’s start with the previous sample sentence in English, our base language in this example: "By using our service, you agree to our {Terms and Conditions} and {Privacy Policy}." The placeholders {Terms and Conditions} and {Privacy Policy} represent URLs. While their content and placement will vary by language, they must remain clickable. Because sentence structures can differ across languages, these placeholders should be handled dynamically to fit each language's syntax. Step 2: Create the Translatable String with Placeholders In your i18n file in key-value format, you’ll define the placeholders that will be replaced by URLs. Using names ones instead of symbols ensures clarity and will prevent errors: { "terms_and_privacy": "By using our service, you agree to our {terms} and {privacy}." } Here I have renamed them simplified as {terms} and {privacy} to avoid issues in code. Best practice is to use simple, descriptive keys in snake_case or camelCase. Step 3: Define the URLs and Anchor Texts Per Language Now, define not just the URLs, but also the translated anchor texts. You’ll need to handle these separately for each language. For English: const en = { terms: ' Terms and Conditions ', privacy: ' Privacy Policy ' }; For Japanese: const ja = { terms: ' 利用規約 ', privacy: ' プライバシーポリシー ', }; This gives flexibility, ensuring that both the anchor text and links are localized and reusable. Step 4: Use the format() Function for Dynamic Replacement Now comes the key step: using the format() method to replace the placeholders with the actual URLs. While JavaScript doesn’t have a built-in method like some other languages such as Python, we can define our own formatting function to replace placeholders dynamically like this: String.prototype.format = function (replacements) { return this.replace(/{(\w+)}/g, (match, key) => typeof replacements[key] !== 'undefined' ? replacements[key] : match ); }; // Example of localized string from i18n file const localizedString = i18n.t('terms_and_privacy'); // Example for English const formattedStringEn = localizedString.format(en); console.log(formattedStringEn); // Output: By using our service, you agree to our Terms and Conditions and Privacy Policy . // Example for Japanese const formattedStringJa = localizedString.format(ja); console.log(formattedStringJa); // Output: 本サービスを利用することにより 利用規約 および プライバシーポリシー に同意したものとみなされます。 The format() function searches for placeholders in curly braces, like {terms} or {privacy}, and replaces them with the corresponding values from the urls object. Note for developers: you will see that I have inserted raw HTML into the strings, just for visual aid and demonstration purposes. In a production app, always sanitize them to prevent injection. Final Thoughts Combining placeholder-based translation strings with dynamic formatting gives you flexibility and control. It keeps your source clean and helps avoid many technical issues as well as human errors. There are different placeholders, depending on the language, framework, or the type of argument they represent. While the Microsoft internationalization guidelines suggest giving translators control over placeholder naming, the key I think here is ensuring that developers and translators share a clear understanding of what each placeholder signifies. To quote an example from their guidelines, to express Available Monday to Friday, from 8 AM to 5 PM Central Time, the first version will be easier to understand than the second one: Available {firstWeekDay} to {lastWeekDay}, from {startTime} to {endTime} {timeZone}. vs. Available %s to %s, from %s to %s %s. To wrap up, here’s a quote I like from Microsoft: When working on any software project, it's important to think about internationalization from the very beginning of its design. Your first iteration or release may not be translated into another language, but the decisions that you make early in the project can determine your ability to deliver to other markets later. […] It's important to ensure all our features are world-ready.
アバター
翻訳が必要な文章に含まれるURLや日付、時刻などの変数の取り扱いは、ローカライゼーションにおいてよくある課題です。これらの位置やフォーマットは言語によって大きく異なるため、多言語対応を同時に進めるような場面では、文字列の管理が非常に複雑になる場合もあります。 こんにちは、KINTOテクノロジーズでローカライゼーションを担当しているMayaです。本記事は、社内で多言語コンテンツを扱いつつも開発バックグラウンドはなく、それでもエンジニアと連携しながら効率的な仕組みをつくりたい!と思い悩んでいる方にぜひお役に立てば良いなという思いで書きました。 コーディング経験がなくても大丈夫です。実装のサポートをいただける同僚がいると理想的ですが、最近はソース言語と翻訳データをエンジニアチームと一緒に整えるための情報もオンラインに多く公開されています。 ちなみに私たちのチームでは、TMS はLokalise を翻訳データのSingle Source of Truthとして使用しており、GitHub に連携しています。 なぜこの仕組みの理解が重要か ローカライゼーション担当者がURLや日付のような動的プレースホルダーの扱い方を理解することで、作業効率が上がるだけでなく、開発チームとの認識ギャップも縮まる期待が見込めます。わかりやすく説明してくれた開発チームのWenさんに感謝です! このアプローチを導入してから(特に利用規約や日付・時間などプレースホルダーを多く含む文の)翻訳対応箇所の修正工数が月に5件ほどからほぼゼロに削減されたのです。時間の節約だけでなく、QAとのやり取りも減り、ローカライゼーションと開発の間の信頼関係も向上しました。 課題:言語ごとに異なるURLの位置 では本題の課題を見てみましょう。以下のような文章があるとします。 "本サービスを利用することにより、利用規約 および プライバシーポリシー に同意したものとみなされます。" 表現は一旦目を瞑って頂きたいのですが、英語版が以下の形だったとして、URLの位置は英語とは異なります。 "By using our service, you agree to our Terms and Conditions and Privacy Policy." さらに3つ目の言語が加われば、その配置はまた異なるかもしれません。多言語を同時に扱う場合、これらの構文差を手動で管理するのはとても手間がかかり、ミスも起こりやすくなります。 解決策:formatString() を使おう ここで登場するのが、この関数です。言語ごとにURLの配置を柔軟に変えられるテンプレートを作成できるため、非常に役立ちます。文全体を各言語ごとに書き換える必要がなく、URLの位置だけを調整できます。 多くのチームが直面している面倒な代替手段は、各言語の文字列にURLを直接ハードコーディングしてしまうことです。これにより、ほとんど同じ文のバリエーションを手作業で複数作成することになり、文字列ファイルが煩雑になり、タイプミスやリンク切れのリスクが高まり、URL変更時のメンテナンスが非常に大変になります。 この記事では、JavaScriptを例にこの課題への解決策を紹介しますが、同様の考え方はPython、C#、PHPなど他の言語でも応用可能です。 ステップ 1:文中の動的要素を特定する まず、英語(ベース言語)の例文を見てみましょう: “By using our service, you agree to our {terms and conditions} and {privacy policy}.” ここで {terms and conditions} と {privacy policy} はURLに置き換えるプレースホルダです。これらの内容と配置は言語ごとに異なりますが、リンクとして機能する必要があります。言語ごとに文構造が異なるため、プレースホルダは各言語の構文に合わせて動的に扱う必要があります。 ステップ 2:プレースホルダ付きの翻訳対象文字列を作成する i18nファイル内のキーと値の形式で、プレースホルダ付きの文を定義します。記号ではなく名前付きプレースホルダを使うことで、より明確でミスを防ぎやすくなります。 json CopyEdit { "terms_and_privacy": "By using our service, you agree to our {terms} and {privacy}." } ここでは、コード上の混乱を避けるため、{terms} と {privacy} に簡略化しています。ベストプラクティスとしては、snake_case や camelCase の簡潔で説明的なキーを使うのが良いでしょう。 ステップ3:言語ごとにURLとリンクテキストを定義する 次に、URLだけでなくリンクテキストも翻訳して定義します。言語ごとに個別に処理する必要があります。 英語の場合: js CopyEdit const en = { terms: ' Terms and Conditions ', privacy: ' Privacy Policy ' }; 日本語の場合: js CopyEdit const ja = { terms: ' 利用規約 ', privacy: ' プライバシーポリシー ' }; この方法により、リンクテキストとURLの両方を翻訳・再利用可能な形で柔軟に扱えます。 ステップ4:format()関数で動的にプレースホルダを置換する ここがポイントです。format() メソッドを使って、プレースホルダを実際のURLに置き換えます。 JavaScriptにはPythonのような組み込みの format() メソッドはありませんが、以下のように自作することで同様の機能が実現できます: js CopyEdit String.prototype.format = function (replacements) { return this.replace(/{(\w+)}/g, (match, key) => typeof replacements[key] !== 'undefined' ? replacements[key] : match ); }; 例: js CopyEdit // i18nファイルから取得した翻訳済み文字列 const localizedString = i18n.t('terms_and_privacy'); // 英語版 const formattedStringEn = localizedString.format(en); console.log(formattedStringEn); // 出力:By using our service, you agree to our Terms and Conditions and Privacy Policy . // 日本語版 const formattedStringJa = localizedString.format(ja); console.log(formattedStringJa); // 出力:本サービスを利用することにより 利用規約 および プライバシーポリシー に同意したものとみなされます。 この format() 関数は {terms} や {privacy} といった中括弧付きのプレースホルダを検索し、指定されたオブジェクトの値で置換します。 開発者向けメモ:ここでは視覚的にわかりやすくするために生のHTMLを挿入していますが、本番環境では必ずサニタイズ処理を行い、インジェクション対策をしてください。 最後に プレースホルダベースの翻訳文字列と動的フォーマットを組み合わせることで、柔軟性と制御性を確保できます。ソースコードをクリーンに保てるだけでなく、技術的な問題や人的ミスの回避にもつながります。 プレースホルダの形式は言語やフレームワーク、引数の種類によって異なります。Microsoftの国際化ガイドラインでは、翻訳者がプレースホルダ名を制御できるようにすべきとされていますが、最も重要なのは「開発者と翻訳者が、それぞれのプレースホルダが何を意味するのかを正しく理解し合うこと」だと思います。 例として、以下の2つの書き方を比べると、最初のものの方が理解しやすいです: css CopyEdit Available {firstWeekDay} to {lastWeekDay}, from {startTime} to {endTime} {timeZone}. よりも: perl CopyEdit Available %s to %s, from %s to %s %s. Microsoftの引用より ソフトウェアプロジェクトを進める際は、最初の段階から国際化を意識することが重要です。最初のバージョンやリリースでは翻訳が不要かもしれませんが、初期の設計での判断が将来的に他市場向けに展開できるかどうかを左右します。…すべての機能を「ワールド・レディ(世界対応)」にしておくことが大切です。 参考リンク https://learn.microsoft.com/en-us/globalization/methodology/international-design
アバター
はじめに こんにちは、グローバル開発部のYao, Bahng, Laiです。 普段は、 Global KINTO App のモバイルアプリエンジニアとしてアプリ開発を行っています。 数ヶ月前、Global KINTO Appの今後に関する事前アクティビティとして、Kotlin Mltiplatform Mobile (KMM)について調査しました。これについては私たちの 前回の記事 をご覧ください。 前回の調査の結果、KMMはプロダクトを迅速に開発するための非常に優れた解決策であることを示していました。 今回、Compose UIと一緒に機能する新しいアプローチのようなKMMが明らかになったので、私たちはKMMについてさらに調査することにしました。 この記事では、その調査に基づいて、Kotlin Multiplatform Mobile(KMM)とCompose Multiplatformを利用したアプリ開発についてお話します。 記事の本題に入る前に、まず以下の3つを説明します。 KMPとは? KMMとは? KMPとKMMの関係は? 回答は以下の通りです: KMP = Kotlin Multiplatform、Kotlinを利用して、複数のプラットフォームに対応したアプリケーションを開発するための技術や、それに関連するエコシステム全体を指しております。 KMM = Kotlin Multiplatform for mobile、KMPの主な利用ケースの一つは、モバイルプラットフォーム間でのコード共有です。KMPに加えて、いくつかのモバイルアプリケーション開発に特化した技術を総称して、「KMM」という名称で呼ばれております。 KMPとKMMの関係を説明するためにグラフを描きました。 参考: -- JetBrains "Kotlin brand assets | Kotlin. (n.d.-c). Kotlin Help." , "Get started with Kotlin Multiplatform for mobile | Kotlin. (n.d.). Kotlin Help." Accessed June 1, 2023 クロスプラットフォーム KMMがクロスプラットフォームのソリューションの一つだという前提で、クロスプラットフォーム開発のメリットについて疑問に思われるかもしれません。以下にその利点を記載します。 コスト効率が良い:クロスプラットフォーム開発により、複数のプラットフォームで単一のコードベースを使用できるため、プラットフォーム別の開発チームが必要なくなり、アプリ開発のコストが削減されます。 より速いデプロイメント:単一のコードベースを活用することで、開発者は複数のプラットフォームで同時にアプリケーションを作成およびローンチできるため、開発時間が大幅に短縮され、リリースまでの時間が短縮されます。 メンテナンスとアップデートの簡素化:単一のコードベースを使用することで、アプリケーションのメンテナンスとアップデートが容易になり、変更を一度行うだけですべてのプラットフォームに反映されます。これにより、メンテナンスプロセスが合理化され、すべてのユーザーが最新の機能を利用できるようになります。 一貫したユーザーエクスペリエンス:クロスプラットフォーム開発ツールとフレームワークを使用することで、異なるプラットフォーム間で一貫したルックアンドフィールを維持でき、統一されたユーザーエクスペリエンスが提供されます。これにより、ユーザー満足度やユーザー維持率の向上に繋がる可能性があります。 共有リソースとスキル:クロスプラットフォームツールと言語に精通した開発者は、複数のプラットフォーム向けのアプリケーションを作成することができます。これにより、リソースの効率的な使用が可能となり、開発者のスキルやトレーニングへの投資のリターンを最大限に引き出すことができます。 モバイル向けクロスプラットフォーム開発ツールの歴史 2009年にPhoneGapが作成され、その後Apache Cordovaに改名されました。 2011年には、MonoによってXamarinが作成され、後にMicrosoftに買収されました。 2015年には、Facebook(Meta)によってReact Nativeが作成されました。 2010年代半ば、デザイナーのFrances BerrimanとGoogle ChromeエンジニアのAlex Russellが「プログレッシブ ウェブ アプリ」という用語を作り出し、Googleはそれを普及するためにいくつかの努力を行いました。2017年には、GoogleによってFlutterが作成されました。 2021年には、JetBrainsによってKMMが作成されました。 つまり、現時点ではKMMが最新のクロスプラットフォームのソリューションです。 ロゴ出典: -- Apache "Artwork - Apache Cordova. (n.d.)." Accessed June 1, 2023 -- Microsoft "Conceptdev. (n.d.). Xamarin documentation - Xamarin. Microsoft Learn" Accessed June 1, 2023 -- Meta "Introduction · React native." Accessed June 1, 2023 -- Google "Progressive web apps. (n.d.). web.dev." Accessed June 1, 2023 -- Google "Flutter documentation. (n.d.)." Accessed June 1, 2023 -- JetBrains "Kotlin Multiplatform for Cross-Platform development" Accessed June 1, 2023 なぜKMMが他と違うのか? 共有ビジネスロジック:KMMは、ビジネスロジック、ネットワーク、データストレージに関連するコードをプラットフォーム間で共有できるようにすることで、コードの重複を減らし、AndroidとiOS間の一貫性を維持します。 真のネイティブUI:KMMは、UI開発にプラットフォーム固有のツールや言語(例:Android用のXML、iOS用のSwiftUIやUIKit)を使用し、他のクロスプラットフォームソリューションと比較してよりネイティブなルックアンドフィールを実現します。 パフォーマンス:Kotlinコードは各プラットフォーム用のネイティブバイナリにコンパイルされるため、ネイティブ開発と同等の高性能なアプリケーションが実現されます。 シームレスな統合:KMMは既存のプロジェクトに統合できるため、開発者は段階的に採用し、アプリを完全に書き換えることなくKotlinへの共有ロジックを移行できます。 ネイティブライブラリとの相互運用性:KMMは、AndroidとiOSの両方のネイティブライブラリとシームレスに相互運用できるため、開発者は既存のライブラリやフレームワークを簡単に活用できます。 Kotlin言語の利点:Kotlinは、モダンで簡潔な言語であり、冗長なコードを減らしながら、既存同等の機能を提供でき、JetBrainsからツールサポートも提供されています。 上記のポイントについて、以下に詳しく説明いたします。 ① Shared Business Logic 共有ビジネスロジックについて 新しいプロジェクトでデータ、ビジネス、プレゼンテーション層を実装する際、KMMを使用します。 柔軟性:KMMは、共有したいコードの範囲を開発者が決定できるため、必要に応じてプラットフォーム固有のコードとバランスの取れた柔軟な実装が可能です。 整合性の担保:UIの違いはQAテストで容易に発見できますが、ロジック部分でAndroid/iOSの不整合を発見することは難しいです。 KMMを使用することで同じコードを用いることができるので、整合性を担保することができます。 ② Truly Native UI 真のネイティブUIについて KMMはネイティブなUIをサポートし、ネイティブなUIコンポーネントを使用し、プラットフォーム固有のデザインパターンに従います。 Android:xml、Jetpack Composeなど iOS:UIKit、SwiftUIなど UIパフォーマンス:KMMはネイティブUIコンポーネントを使用し、Kotlinコードが各プラットフォーム用のネイティブバイナリにコンパイルされるため、パフォーマンスは一般的にネイティブアプリケーションと同等です。 容易なプラットフォームの更新:KMMを使用すると、開発者は新しいプラットフォーム機能やデザインの更新を簡単に行うことができます。各プラットフォームのネイティブUIフレームワークを使用しているためです。 ③ Performance パフォーマンスについて JavaScriptブリッジは不要で、サードパーティーのライブラリに依存しません。 独立したレンダリングエンジンを使用する必要はなく、システムデフォルトのレンダリングエンジンを使用するため、他のクロスプラットフォームソリューションと比較してリソース消費が少なくなります。 ネイティブコードのコンパイル:KMMは、Kotlinコードを各プラットフォームのネイティブバイナリにコンパイルします。このネイティブコードのコンパイルにより、アプリはより効率的に実行され、パフォーマンスが向上します。 Android:標準のKotlin/JVM iOS:Kotlin/Nativeコンパイラ(Objective-C) ④ Seamless Integration シームレスな統合について ネイティブモジュールをブリッジする必要はなく、既存のコードを書き換える必要もありません。 段階的な採用:KMMは、既存のネイティブAndroidおよびiOSプロジェクトに徐々に導入できます。これにより、チームはビジネスロジック、ネットワーク、データストレージコードを段階的にプラットフォーム間で共有でき、完全な技術切り替えに関連するリスクを軽減できます。 iOSでKMMモジュールを利用する複数のアプローチ CocoaPods Gradleプラグインとgitサブモジュール Framework Swift Package Manager(SPM):Kotlin 1.5.30から、Swift Package Managerを使用してiOSプロジェクトでKMMモジュールを利用できます ⑤ Interoperability with Native Libraries ネイティブライブラリとの相互運用性について ネイティブAPIおよびライブラリへのアクセス:KMMはネイティブAPIとライブラリへ直接アクセスできるので、センサーやBluetoothなどのプラットフォーム固有の機能やハードウェアコンポーネントと簡単に統合することができます。 プラットフォーム固有のコードとのシームレスな統合:KMMは、必要に応じてプラットフォーム固有のコードを記述できるようになっています。これは複雑なネイティブライブラリを扱う場合や、共有Kotlinコードを通して利用できない機能にアクセスが必要な場合に有効です。 Kotlin/Native:KMMは、iOS向けにKotlin/Nativeを使用します。これにより、Objective-CおよびSwiftコードとのシームレスな相互運用性が実現されます。これは、既存のiOSライブラリやフレームワークを追加のブリッジングやラッピングコードなしで使用できることを意味します。 ⑥ Kotlin Language Benefits Kotlin言語の利点について 言語機能:モダンで静的型付け、null安全、拡張関数、データクラス、スマートキャスト Javaとの相互運用性 ツールおよびサポート:Kotlinは、優れたツールサポートを提供し、Android StudioおよびIntelliJ IDEAでの第一級の統合が行われています。 業界による採用:Kotlinは、Android開発の公式プログラミング言語になって以来、急速に採用が進んでいます。多くのバックエンド開発者もKotlinを使用しています。 KMMを利用しているのは、どのような方々でしょうか 実際に複数の企業がKotlin Multiplatform Mobile(KMM)をモバイルアプリ開発に採用しています。いくつかの注目すべき例を挙げます。 Netflix:Netflixは、AndroidアプリとiOSアプリ間でコードを共有するために、一部の内部ツールでKMMを使用しています。 VMware:VMwareは、Workspace ONE Intelligent Hubアプリ(AndroidおよびiOS向けの従業員管理ツール)のクロスプラットフォーム開発にKMMを使用しています。 Yandex:ロシアの多国籍技術企業であるYandexは、Yandex MapsやYandex Diskを含むいくつかのモバイルアプリでKMMを採用しています。 Quizlet:オンライン学習プラットフォームであるQuizletは、AndroidアプリとiOSアプリ間でコードを共有するためにKMMを使用し、開発効率を向上させています。 これらの企業は、多様な業界を代表しており、KMMの採用は、異なるコンテキストでの技術の柔軟性と有用性を示しています。KMMがさらに普及するにつれて、より多くの企業がクロスプラットフォームのモバイル開発のニーズに対応するためにKMMを採用することが予想されます。 参考: -- JetBrains "Case studies. (n.d.). Kotlin Multiplatform." Accessed June 1, 2023 KMMプロジェクトを簡単に作成する方法 上記のメリットを考慮した上で、KMMプロジェクトを作成して試してみたいと思われますか?以下に方法をご案内いたします。 最新のAndroid Studioをダウンロードします。 Android Studioで、ファイル|新規|新規プロジェクトを選択します。 プロジェクトテンプレートのリストでKotlin Multiplatform Appを選択し、Nextをクリックします。 最初のアプリケーションに名前を付け、Nextをクリックします。 iOSフレームワーク配布リストで、Regular frameworkオプションを選択します。 アプリケーションと共有フォルダのデフォルトの名前を維持します。Finishをクリックします。 -- JetBrains "Create your first cross-platform app | Kotlin. (n.d.). Kotlin Help." Accessed June 1, 2023 KMMを使用したモバイルアプリのアーキテクチャ 以下のグラフは、KMMの標準的なパターンの一つの例です。 このアーキテクチャは、KMMの特性であるコード共有を最大限に活用しています。キャッシュ、データベース、ネットワーク、ユースケース、ビューモデルを含むデータ永続化は、すべてKMMで実装されています。 UIについては、AndroidとiOSの両方でネイティブUIコンポーネントを使用しています。XMLやUIKitなどの古いフレームワークから、Jetpack ComposeやSwiftUIを含めた新しいフレームワークもサポートされています。 このアーキテクチャではKotlinで書かれたビジネスロジックモジュールをSDKとしてiOSにインポートすることができます。これにより、iOS開発者はUI開発に集中でき、効率的な開発が実現できます。 下記はFAQリストを表示する簡単な画面のiOS側の実装になります。共通のUI Utility Classを除くと実装が必要なものはこれで終わりです。 #FaqView.swift struct FaqView: View { private let viewModel = FaqViewModel() @State var state: FaqContractState init() { state = viewModel.createInitialState() } var body: some View { NavigationView { listView() } .onAppear { viewModel.uiState.collect(collector: Collector<FaqContractState> { self.state = $0 } ) { possibleError in print("finished with possible error") } } } private func listView() -> AnyView { manageResourceState( resourceState: state.uiState, successView: { data in guard let list = data as? [Faq] else { return AnyView(Text("error")) } return AnyView( List { ForEach(list, id: \.self) { item in Text(item.description) } } ) }, onTryAgain: { viewModel.setEvent(event: FaqContractEvent.Retry()) }, onCheckAgain: { viewModel.setEvent(event: FaqContractEvent.Retry()) } ) } } それがKMMの全てではありません。KMMにはさらなる可能性があります! UIコードを共有するアーキテクチャ ビジネスロジックコードだけでなく、KMMはCompose Multiplatformを使用してUIコードも共有することができます。 先述のように、KMMは主に共有ビジネスロジックコードの実装に使用されますが、共有UIにも対応しています。Compose Multiplatformは、Kotlinで複数のプラットフォーム間でUIを共有するための宣言的フレームワークです。Jetpack Composeをベースに、JetBrainsとオープンソースの貢献者によって開発されました。KMMとCompose Multiplatformを組み合わせることで、Kotlin言語を使用してロジックコードとUIを両方構築できます。 参考: -- JetBrains "Kotlin brand assets | Kotlin. (n.d.-c). Kotlin Help." , "Compose multiplatform UI framework | JetBrains. (n.d.). JetBrains: Developer Tools for Professionals and Teams." Accessed June 1, 2023 KMMのアーキテクチャの異なるパターンの比較 モバイルプロジェクトを開発すると仮定し、各クライアントの工数は次のとおりです: UI:2人、Presentation:1人、Business/Domain:1人、Data/Core:1人 ここから削減できる工数はKMMによって書かれたコードの割合に基づきます。 パターン A B C D UI 2*2 2*2 2*2 2 Presentation 1*2 1*2 1 1 Business/Domain 1*2 1 1 1 Data/Core 1 1 1 1 合計 9 8 7 5 工数コスト -10% -20% -30% -50% KMMを用いることで最大で50%もの工数を削減することができます。 他のクロスプラットフォームソリューションと比較して、KMMの最大の利点は、KMMが柔軟にコード共有できることです。 KMMでどれだけのコードを共有するかは、完全に私たち自身で決定できます。 他のクロスプラットフォームソリューションではここまでの柔軟性がないため、こうはいきません。 まとめ KMMの欠点 もちろん、すべてのツールにはデメリットがあります。KMMにもデメリットがあります。 限られたプラットフォームのサポート: Kotlin Multiplatform Mobileは複数のプラットフォームを対象とできますが、すべてのプラットフォームをサポートしているわけではありません。たとえば、現時点ではWebやデスクトップアプリケーションをサポートしていません。 学習コスト: Kotlinに慣れていない場合、マルチプラットフォーム開発に効果的に使用するための学習コストが必要です。 フレームワークの互換性: Kotlin Multiplatform Mobileはさまざまなフレームワークと共に使用できますが、すべてのフレームワークと互換性があるわけではありません。これにより、オプションが制限され、特定の制約内で作業する必要があるかもしれません。 メンテナンスのオーバーヘッド: マルチプラットフォームのコードベースを維持することは、各プラットフォームごとに別々のコードベースを維持するよりも複雑になる可能性があります。これにより、テスト、デバッグ、メンテナンスにおいて追加のオーバーヘッドが生じる可能性があります。 ツールの制限: 一部のツールやライブラリがKotlin Multiplatform Mobileと互換性がない場合があり、開発がより困難になるか、代替のソリューションを探す必要があるかもしれません。 応用 上記の通り、メリットやデメリットを含めて、さまざまな状況に応じてKMMのアーキテクチャをプロジェクトに統合することが検討できます。 パターン A B C D 一般的な既存プロジェクト ✓ ✓ ✓ ? シンプルな既存のプロジェクト ✓ ✓ ✓ ✓ 複雑な既存プロジェクト ✓ ✓ ✓ ✗ 新しいプロジェクト ✓ ✓ ✓ ✓ プロトタイプ ✓ ✓ ✓ ✓ 技術的な利点を一旦ここまでとして、実開発プロセスの話題に戻ります。多くモバイル開発チームと同様、私たちのチームも規模が小さいです。限られたエンジニアリソースの中で、バージョン1.0から2.0へのアップグレードといった比較的大規模な変更に直面した場合、我々は他の部署やオンサイトまたオフショアのアウトソーシングチームと連携し、リリースの迅速化を確実にする必要があります。 しかし、この過程に置いて、以下のような複数の問題が存在します: 異なるチーム間のシームレスな連携が難しい。 開発者が増え、異なるオフィスに所属する異なるチームとなると、コミュニケーションコストが上昇します。 異なるチーム間での一貫性を維持することが難しくなります。 外部チームとの連携において、機密情報のセキュリティ管理が難しくなります。 KMMはコアモジュールの開発を行い、プロトコルを定義し、UIとロジックを分離した開発というアプローチを取ることで、これらの問題をほぼ全て解決することができます: 各チームが自分のパートに専念することが可能になります。協力作業を大幅に容易にすることができます。 コミュニケーションに必要な時間やコストを削減することができます。 コアモジュールはKMMチームが開発することで、同じ基盤で開発を行うことで、ほとんどの不整合が事前に解消されます。 KMMは一つのコードベースをサポートしているものの、UI層とロジック層が分離しているため、複数のリポジトリを利用することも可能です。コアモジュールはKMMチームが開発し、SDKは外部チームに提供します。これにより、ソースコードが外部チーム公開しする必要がなく、機密情報の漏洩リスクを減らすことができます。これは他のクロスプラットフォーム技術のソリューションでは達成が難しいです。 結論として、KMMは技術的な利点だけでなく、部門や会社を超えた協力作業にも多大な利点をもたらしてくれると言えます。 最後に 新プロジェクトにおけるKMMの重要性と、大幅な工数削減の可能性を考慮し、すでに次のメジャーリリース向けの新プロジェクトにKMMを統合しました。私たちは引き続きKMMに関連する新たな技術やツールに留意し、さらなる効率向上を図るための可能性を探し続けます。
アバター
はじめに こんにちは、KINTOテクノロジーズ(以下、KTC)プロジェクト推進GのRisako.Nです。 KTCには2023年3月に入社して、PjMという役割を担っています。キャリアとしてはずっとSIerで、新卒当初は主にWebベースの業務システムをゴリゴリ開発、その後いわゆる上流工程へどんどんスライドし、ここ数年はほぼPMを担ってきました。 今回の記事では、SIerでのPM経験を踏まえつつ、KTCのPjMの仕事をご紹介したいと思います! プロジェクトとは KTCでは基本的にプロダクト(以下、PD)ごとに開発チームやPdMがいて、開発チームはKINTOの各事業部門とコミュニケーションをとりながら、日々PDの機能強化や改善案件に対応しています。また案件は事業部門から起案されるものだけではなく、法令対応やアーキテクチャの更改など開発チームから起案するものもあり、常に複数の案件が並行して動いています。 そして案件によっては、実現したい1つのことに対して複数のPD・複数の事業部門を跨って対応が必要なものもあります。こういった部門横断的に進めていく必要があるもの、ある一定期間以上(あくまで目安ですが4〜5ヶ月以上)の長さがあり、かつ有期的な案件を「プロジェクト」と呼び、その「プロジェクト」に対して実行責務をもったPjMを配置して、プロジェクトを実行・推進していきます。 プロジェクトはどう発足されるのか 次にプロジェクトがどう発足されるのか、その流れについてご紹介します。KINTO/KTCでは、プロジェクトは以下のステップを踏んでいくことになります。 企画の起案 企画としての成立性(売れる商品か、収益は担保できるかなど)に対する社内合意・承認 企画を実現するために関係各部よりメンバーがアサインされて、プロジェクトが組成・発足 この3ステップ目のプロジェクト組成のタイミングで、事業側・システム側それぞれの推進役であるビジネス統括・システムPM(=PjM)がアサインされ、このビジネス統括とシステムPMを中心にプロジェクトを立ち上げ、実行・完遂させていくことになります。 このようにPjMがアサインされるのは基本的にプロジェクトを立ち上げる(企画がほぼ形作られた)タイミングとなるわけですが、KTCには企画段階からシステム開発視点をもって企画やプロジェクトの立ち上げを支援する「プロデュースG」もあります。プロデュースGはどの案件にも常に参画しているわけではないのですが、もしアサインされるプロジェクトにプロデュースGが関係している場合はPjMはプロデュースGからこのプロジェクトの背景や向かう方向性、現在の課題など引継ぎながら、プロジェクトを立ち上げ、実行フェーズへと突入させていきます。 …と、基本的には上述の流れなのですが、そのプロジェクトごとに個別の状況があり、また携わるメンバーも様々であるため、プロジェクトごとに立ち上がり方は様々です。 例えば、 要件定義までは昨年度終わっていて満を持して今年度再開したプロジェクト 昨年度には全く企画されていなかったけれど急遽企画があがり走り出したプロジェクト などなど またいったん企画が走り出したものの、検討するにつれて見えてきたものや環境の変化により、推進が凍結されたプロジェクトもあります。こういった立ち上げや意思決定のスピード感はKINTO/KTCならではかなと思います。 プロジェクトの進め方とPjMとしてやること プロジェクトには色々なものがありますが、例えばKINTO ONEの新しいプランを打ち出そう!というKINTO ONE商品開発プロジェクトを例にすると、プロジェクト全体という大きな視点で見ると、シンプルにウォーターフォールで進めることが多いです。 <ウォーターフォール型プロセス> 「プロジェクト全体」という表現をしたのは、そのプロジェクトに参画しているPDは、 ユーザーストーリー単位で、設計・開発・テスト工程を繰り返すPD 全機能設計して、開発・テストと進めていくPD といったように、PDチームごとに開発の進め方が異なるためです。 このプロセスの中でPjMは何をするのかというと、 プロジェクトを立ち上げる段階では、今回のプロジェクトの目的やビジネス要求をよく理解し、事業部門と要件の検討、調整を行いながらシステム要件定義を推進 要件定義後は基本的にPDごとに設計開発を進めることになりますが、各PDの開発規模はそのプロジェクトによって大小様々、各PDは他の案件も並行して進めている状況なので、各PDどのような開発スケジュールで進めていくのかを把握し、定期的に進捗状況を確認しながら案件の進捗管理 各PDが開発を進めている間に、PD合流後のテスト準備、QAチームとのテスト調整 要件定義後に挙がってくる追加/変更要望、リリース日の決定など、事業部門との調整全般 などです。 PjMを実際やり始めてみて PjMの役割は「プロジェクトのゴールに対して実行計画をたて実行、推進、完遂までやりとげること」であり、役割自体は他の会社でも多く同じなのではと思います。ただわたしのこれまで経験してきたSIerプロジェクトと比べてみると、よりビジネスに近い立場でプロジェクトの推進役を担っているように思います。これまでシステムコンサル、PjMどちらの参画としてもやはり事業側とは受発注の関係であったため、入り込めない(また、入り込むべきではない)壁がありましたが、KINTOとKTCは会社そのものは別としても基は同じなので、良い意味で分け隔てなく意見を飛び交わせ、協力して進めていきます。 PjMとしてはシステム開発を計画・主導していくことはもちろんですが、ビジネスをよく理解したうえでシステム観点からKINTOの事業拡大に寄与できるように、またビジネス+システムというプロジェクト全体のマネジメントを視野に入れて経験を積んでいきたいと思っています。 そしてKTCでPjMをやり始めて難しいと思ったことは、KTCにはデザイナーから商用サイト、業務システム開発と様々な分野に携わるメンバーがいて、それぞれ様々な経験を持って集まってきているため、今まで常識と思っていたことが常識ではない、やり方も個々それぞれのやり方があってそれを尊重しつつ、でもリードもしていかないと!というところです。 またビジネス面としても、実際わたしは入社して2ヶ月目で新しいプランを打ち出すプロジェクトにPjMとして携わったのですが、KINTOではどうやって新プランを作り上げていくのか、リース業として検討・ケアするべきことは何か、自動車業界ではどういったことが起こりうるのか(どういったリスクが潜んでいるのか)など、わからないことだらけでした。ただわからないこと1つずつに対して、過去プロジェクトの情報を収集する、まわりの人に聞くなどしてわかることに変えていきながら、プロジェクトとしての立ち上げや推進はこれまでの経験を活かして前進!でした。 プロジェクトに参画して推進するためには、関係者の方々との関係性も築く必要があるので、基本的にはオンラインで会話しつつも、直接会って話をするために積極的に名古屋出張にも行きました。 ちなみにKINTO名古屋オフィスは「来た人が楽しくなるオフィスに」という想いのあるデザイン設計がされていまして、ちょっとオシャレなのです!( 初公開!KINTO名古屋オフィスツアー でご紹介しています!)加えて、オフィスの近くには柳橋中央市場(名古屋中心街に急に現れる本格的な市場!)があったり、有名パン屋さんがあったり。。出張っていいですよね! ![](/assets/blog/authors/risako.n/nagoya-morning.png =400x) 名古屋といえばモーニング!コーヒーはデフォルト2杯でした ![](/assets/blog/authors/risako.n/nagoya-food.png =400x) 名古屋セットです! ![](/assets/blog/authors/risako.n/tebasaki.png =400x) 色んな食べ方があるみたいです。一緒に食べていた人はどれでもないって言ってたような… このようにわからない環境に飛び込むことやその中で物事を前進させることは簡単ではないですが、新たな知識や経験、気づきを得ることは面白く、これからも色んな人と出会って自分自身の幅を広げていきたいな〜と思っています。 さいごに KTCはまだ若い会社、かつ取り巻く環境もどんどん変化しているため、プロジェクトの立ち上げ方や推進手法も様々です。その分新しい発見も多く、また自分の意思を持って進めることができる環境にあると思います。そういった環境、またPjMという職種に興味を持っていただけた方、ぜひジョインいただいて一緒に働くことができたらうれしいです!
アバター
自己紹介 こんにちは。KINTOテクノロジーズ株式会社(以降、KINTOテクノロジーズ)のPlatform Group/SRE Teamに所属している渡辺宇と申します。WEBサービスのアプリケーション開発・インフラ構築・CI/CD構築・保守運用をしてきた経験を活かして、自社サービスの信頼性向上をサポートしています。 はじめに どんな優れたサービスでも、全く問題が発生しないという状態は現実的には存在しません。そのため、予めどれくらいの問題が発生しても許容範囲内であるかという目標を設定し、場合によってはこれをユーザーと共有し、合意を形成することが現代のサービス提供において重要な考え方となっています。 具体的には、Service Level Indicator(以降、SLI)を用いてサービスレベルの指標を定義し、Service Level Objective(以降、SLO)でその目標値を設定します。そして、Service Level Agreement(以降、SLA)を通じて、これらの目標値についてユーザーと合意を取ります。 SLOを設定したら次のステップは、目標違反が発生していないか監視することです。違反が発生した場合にはアラートを発信する必要があります。しかし、アラートを発信するルールは煩雑で、管理が困難になりがちです。 その問題を解決するために、今回の記事ではSlothというアラートルールのジェネレータを用いたアラートルールの生成と管理の効率化について紹介します。 背景 私たちKINTOテクノロジーズでは、以前に紹介した通り、「Prometheus + Grafana + X-Ray」のスタックを用いてテレメトリデータの取得を実現し、リクエスト・レスポンス型のWEBサービスの可観測性(Observability)を向上させています。 https://blog.kinto-technologies.com/posts/2022-12-09-AWS_Prometehus_Grafana_o11y/ この取り組みのおかげで、特に、Spring Bootのアプリケーションメトリクスについては、アプリケーションコード上で特別に計装することなく、多様なメトリクスをPrometheusに格納できています。 格納されたメトリクスの中には、リクエスト単位の成功・失敗ステータスや応答速度のデータも含まれています。これにより、PromQLを用いて、可用性やレイテンシのSLIを表現することが可能になりました。 課題 一般的に、WEBサービスのCUJ(Critical User Journey)に対してSLI/SLOの定義をした後、本番サービスのエラーバジェットの消費状況を監視します。その際、SLO/SLA違反が発生しそうな場合は適切なタイミングでそれを検知する必要があります。そのためには、開発担当者が異変を検知できるように、アラートを設定しておく必要があります。 サイトリライアビリティワークブックでは、SLO違反の検知には「複数ウィンドウ、複数バーンレートのアラート(Multiwindow, Multi-Burn-Rate Alerts)」の手法が、最も現実的でおすすめできると書かれています。適合率・再現率・検出時間・リセット時間をうまくコントロールできるメリットがある、とのことなので積極的に使いたいです。 ウィンドウとバーンレートについて簡単に説明します: ウィンドウ 計測期間のことを指します。どの時点で計測を開始し、どの時点で計測を終了するかの期間です。SLOは比率で表現されるため、計測期間が終わると次の計測期間開始時にサービスレベルは100%に回復します。一般的に、ウィンドウサイズが大きいほど、アラートは発しにくく、かつ、終了しにくくなります。 バーンレート エラーバジェットの消費速度のことを指します。エラーバジェットを全て消費してからアラートが発されると遅いので、エラーバジェットがある程度消費された時点でアラートを発することが理想的です。“サービスのSLOに紐付くエラーバジェットがウィンドウの終わりにちょうど0になるようなバーンレート”を1(基準)とし、それと比較して何倍の速さでエラーバジェットが消費されているのかを計算し、その結果(基準と比べて何倍か)をバーンレートとして数値で保持します。バーンレートが事前に定められた値を超えた時にアラートが発されるように設定します。 「複数ウィンドウ、複数バーンレートのアラート」について詳しく知りたい方は、サイトリライアビリティワークブックの5章「SLOに基づくアラート」をご覧ください。 https://www.oreilly.co.jp/books/9784873119137/ 英語版はWebで公開されています: https://sre.google/workbook/alerting-on-slos/#6-multiwindow-multi-burn-rate-alerts 「複数ウィンドウ、複数バーンレートのアラート」の手法を利用するには、1つのSLI/SLO定義に対して複数のウィンドウとバーンレート、つまり、複数の異なるパラメータを指定したアラートルールを設定する必要があります。 よって、アラートルールの数が増えるため、アラートルールの管理が難しくなるという課題がありました。 やること 今回の記事では、Slothというオープンソースツールを活用して、この課題を解決していきます。 https://sloth.dev/ Slothを利用すると、シンプルな記述でSLI/SLOの仕様を入力し、それをもとに、複雑でエラーになりやすいPrometheusの記録ルールやアラートルール定義ファイルを生成することが可能となります。 私たちKINTOテクノロジーズでは、下図のような構成を採用しています。 Slothには、デフォルトで複数ウィンドウ、複数バーンレートのアラートルールを生成する機能が備わっています。そのため、この記事ではSlothを使って「複数ウィンドウ、複数バーンレートのアラート」の設定をする方法を紹介します。 アラートルール生成 :::message Slothは、OpenSLOの規格に従ったSLI/SLO仕様ファイルを入力とすることが可能です。しかし、現状ではPrometheusのアラートルール生成に対応していないようなので、今回はSloth独自のSLI/SLO仕様記法を採用しています。 ::: 次の簡単なSLI/SLO仕様を、Slothの規格に基づいたYAMLファイルで表現してみます。 Category SLI SLO Availability 30日のうち、アプリケーションで測定される成功したリクエストの割合。500~599と429以外のHTTPステータスは成功とみなします。actuator以外の全てのリクエストパスを統合して計測します。 99.5% version: "prometheus/v1" service: "KINTO" labels: owner: "KINTO Technologies Corporation" repo: "slo-maintenance" tier: "2" slos: # We allow failing (5xx and 429) 5 request every 1000 requests (99.5%). - name: "kinto-requests-availability" objective: 99.5 description: "Common SLO based on availability for HTTP request responses." sli: events: error_query: sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[{{.window}}])) total_query: sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[{{.window}}])) alerting: name: KINTOHighErrorRate labels: category: "availability" annotations: # Overwrite default Sloth SLO alert summmary on ticket and page alerts. summary: "High error rate on 'KINTO SERVICE' requests responses" page_alert: labels: severity: "critical" ticket_alert: labels: severity: "warning" http_server_requests_seconds_count はSpring Bootを使った場合のメトリクスです。 このファイルを./source/ディレクトリに保存した状態で、次のコマンドを実行します。 docker pull ghcr.io/slok/sloth docker run -v /$(pwd):/home ghcr.io/slok/sloth generate -i /home/source/slo_spec.yml > slo_generated_rules.yml 上記のコマンドを実行すると、カレントディレクトリに下記のファイルが生成されます。生成されたファイルはそのままPrometheusにアップロードできます。 :::details slo_generate_rules.yml --- # Code generated by Sloth (a9d9dc42fb66372fb1bd2c69ca354da4ace51b65): https://github.com/slok/sloth. # DO NOT EDIT. groups: - name: sloth-slo-sli-recordings-KINTO-kinto-requests-availability rules: - record: slo:sli_error:ratio_rate5m expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[5m]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[5m]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 5m tier: "2" - record: slo:sli_error:ratio_rate30m expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[30m]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[30m]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 30m tier: "2" - record: slo:sli_error:ratio_rate1h expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[1h]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[1h]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 1h tier: "2" - record: slo:sli_error:ratio_rate2h expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[2h]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[2h]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 2h tier: "2" - record: slo:sli_error:ratio_rate6h expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[6h]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[6h]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 6h tier: "2" - record: slo:sli_error:ratio_rate1d expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[1d]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[1d]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 1d tier: "2" - record: slo:sli_error:ratio_rate3d expr: | (sum(rate(http_server_requests_seconds_count{application="kinto",status=~"(5..|429)",uri!~".*actuator.*"}[3d]))) / (sum(rate(http_server_requests_seconds_count{application="kinto",uri!~".*actuator.*"}[3d]))) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 3d tier: "2" - record: slo:sli_error:ratio_rate30d expr: | sum_over_time(slo:sli_error:ratio_rate5m{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"}[30d]) / ignoring (sloth_window) count_over_time(slo:sli_error:ratio_rate5m{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"}[30d]) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_window: 30d tier: "2" - name: sloth-slo-meta-recordings-KINTO-kinto-requests-availability rules: - record: slo:objective:ratio expr: vector(0.995) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability tier: "2" - record: slo:error_budget:ratio expr: vector(1-0.995) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability tier: "2" - record: slo:time_period:days expr: vector(30) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability tier: "2" - record: slo:current_burn_rate:ratio expr: | slo:sli_error:ratio_rate5m{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} / on(sloth_id, sloth_slo, sloth_service) group_left slo:error_budget:ratio{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability tier: "2" - record: slo:period_burn_rate:ratio expr: | slo:sli_error:ratio_rate30d{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} / on(sloth_id, sloth_slo, sloth_service) group_left slo:error_budget:ratio{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability tier: "2" - record: slo:period_error_budget_remaining:ratio expr: 1 - slo:period_burn_rate:ratio{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_service: KINTO sloth_slo: kinto-requests-availability tier: "2" - record: sloth_slo_info expr: vector(1) labels: owner: KINTO Technologies Corporation repo: slo-maintenance sloth_id: KINTO-kinto-requests-availability sloth_mode: cli-gen-prom sloth_objective: "99.5" sloth_service: KINTO sloth_slo: kinto-requests-availability sloth_spec: prometheus/v1 sloth_version: a9d9dc42fb66372fb1bd2c69ca354da4ace51b65 tier: "2" - name: sloth-slo-alerts-KINTO-kinto-requests-availability rules: - alert: KINTOHighErrorRate expr: | ( max(slo:sli_error:ratio_rate5m{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (14.4 * 0.005)) without (sloth_window) and max(slo:sli_error:ratio_rate1h{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (14.4 * 0.005)) without (sloth_window) ) or ( max(slo:sli_error:ratio_rate30m{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (6 * 0.005)) without (sloth_window) and max(slo:sli_error:ratio_rate6h{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (6 * 0.005)) without (sloth_window) ) labels: category: availability severity: critical sloth_severity: page annotations: summary: High error rate on 'KINTO SERVICE' requests responses title: (page) {{$labels.sloth_service}} {{$labels.sloth_slo}} SLO error budget burn rate is too fast. - alert: KINTOHighErrorRate expr: | ( max(slo:sli_error:ratio_rate2h{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (3 * 0.005)) without (sloth_window) and max(slo:sli_error:ratio_rate1d{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (3 * 0.005)) without (sloth_window) ) or ( max(slo:sli_error:ratio_rate6h{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (1 * 0.005)) without (sloth_window) and max(slo:sli_error:ratio_rate3d{sloth_id="KINTO-kinto-requests-availability", sloth_service="KINTO", sloth_slo="kinto-requests-availability"} > (1 * 0.005)) without (sloth_window) ) labels: category: availability severity: warning sloth_severity: ticket annotations: summary: High error rate on 'KINTO SERVICE' requests responses title: (ticket) {{$labels.sloth_service}} {{$labels.sloth_slo}} SLO error budget burn rate is too fast. ::: 今回は簡単な例を生成してみましたが、実際にはより複雑なSLI/SLO仕様を複数定義することになるでしょう。Slothを使用しない場合、生成されたような長いコードを直接管理する必要がありますが、Slothを活用すればその手間が大幅に軽減されます。 設定手順 私たちKINTOテクノロジーズでは、「Amazon Managed Service for Prometheus」を活用しています。そのため、AWSのマネージドコンソールを通じて生成ファイルのアップロードが可能です。 「Amazon Managed Service for Prometheus」の詳細な利用方法については公式ドキュメンテーションをご参照ください: https://docs.aws.amazon.com/ja_jp/prometheus/latest/userguide/AMP-rules-upload.html もしくは、AWS CLIをワークフローから実行することもできます。ここでは、GitHub Actionsを利用した例を示します。 name: SLO set up on: workflow_dispatch: jobs: setup-slo: name: Set up SLOs runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set AWS Credentials to EnvParam(Common) uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ 利用するAWSのアクセスキー }} aws-secret-access-key: ${{ 利用するAWSのシークレットアクセスキー }} aws-region: ${{ 利用するAWSのリージョン }} ## 定義ファイルから設定ファイルを生成 - name: download and setup generator binary run: | ## 適宜、最新のリリース状況を確認してください。 wget https://github.com/slok/sloth/releases/download/vX.XX.X/sloth-linux-amd64 chmod +x sloth-linux-amd64 ./sloth-linux-amd64 validate -i ./services/kinto/source/slo_spec.yml ./sloth-linux-amd64 generate -i ./services/kinto/source/slo_spec.yml -o ./services/kinto/configuration.yml ## Prometheusに設定ファイルをアップロード - name: upload configuration file to APM run: | base64 ./services/kinto/configuration.yml > ./services/kinto/configuration_base_64.yml aws amp create-rule-groups-namespace \ --data file://./services/kinto/configuration_base_64.yml \ --name slo-rules \ --workspace-id ${{ 利用するAMPワークスペースのID }} \ --region ${{ 利用するAWSのリージョン }} 可視化 Prometheusへルールファイルのアップロードが完了したら、次に行うのはデータの可視化です。私たちは、Grafanaを使用しています。Slothには生成したルールを可視化するためのダッシュボードのテンプレートがGrafana Labsにあるため、これをインポートすることで可視化することができます。 https://sloth.dev/introduction/dashboards/ アラート設定の手順 「複数ウィンドウ、複数バーンレートのアラート」は、Prometheusから送信されます。アラートマネージャーの設定ファイルを作成し、それをPrometheusにアップロードします。 https://docs.aws.amazon.com/ja_jp/prometheus/latest/userguide/AMP-alertmanager-config.html :::message 「Amazon Managed Service for Prometheus」では、現時点ではAmazon SNSへの通知のみサポートされています(今後の改善に期待しています!)。そのため、事前にSNSトピックを作成し、そのトピックのARNを設定ファイル内で指定しています。 ::: 私たちKINTOテクノロジーズでは、CriticalアラートとWarningアラートのルーティングを分けるため、次のような設定ファイルを作成しています。送信されるSNSアトリビュートには、アラートの種類情報を含めています。 alertmanager_config: | # The root route on which each incoming alert enters. route: # A default receiver receiver: warning_alert routes: - receiver: critical_alert matchers: - severity="critical" - receiver: warning_alert matchers: - severity="warning" # Amazon Managed Service for Prometheus, # The only receiver currently supported is Amazon Simple Notification Service (Amazon SNS). # If you have other types of receivers listed in the configuration, it will be rejected. # Expect future revisions. https://docs.aws.amazon.com/ja_jp/prometheus/latest/userguide/AMP-alertmanager-config.html receivers: - name: critical_alert sns_configs: - topic_arn: arn:aws:sns:{AWS region}:{AWS account}:prometheus-alertmanager sigv4: region: {AWS region} attributes: severity: critical slack_api_url: '<your slack api url>' slack_channel: '#<your channel name>' - name: warning_alert sns_configs: - topic_arn: arn:aws:sns:{AWS region}:{AWS account}:prometheus-alertmanager sigv4: region: {AWS region} attributes: severity: warning slack_api_url: '<your slack api url>' slack_channel: '#<your channel name>' また、SNSトピックはAWS Lambdaで購読しています。Lambdaでは、トリガーとなったアトリビュートを利用し、Slackの通知先チャネルを動的に変更しています。実際には、Criticalアラートが発された場合にPagerDutyのAPIを叩くようにするなど、もっとカスタマイズします。 # # this script based on https://aws.amazon.com/jp/premiumsupport/knowledge-center/sns-lambda-webhooks-chime-slack-teams/ # import urllib3 import json http = urllib3.PoolManager() def lambda_handler(event, context): print({"severity": event["Records"][0]["Sns"]["MessageAttributes"]["severity"]["Value"]}) url = event["Records"][0]["Sns"]["MessageAttributes"]["slack_api_url"]["Value"] msg = { "channel": event["Records"][0]["Sns"]["MessageAttributes"]["slack_channel"]["Value"], "username": "PROMETHEUS_ALERTMANAGER", "text": event["Records"][0]["Sns"]["Message"], "icon_emoji": "", } encoded_msg = json.dumps(msg).encode("utf-8") resp = http.request("POST", url, body=encoded_msg) print( { "message": event["Records"][0]["Sns"]["Message"], "status_code": resp.status, "response": resp.data, } ) 工夫した点 SLOs as Code Slothを活用すれば、SLI/SLO仕様をYAMLファイル形式でコード化できます。これはコードなので、Gitなどを用いてバージョン管理することが可能です。また、GitHubなどのホスティングツールを利用することで、レビューしやすくなります。SLI/SLO仕様がPrometheusで対応可能なもの(PromQLで表現可能なもの)であれば、アプリケーションだけでなく、ロードバランサーや外形監視サービスなどのメトリクス監視にも適用できます。そのため、Slothの応用範囲は広いと言えます。 私たちKINTOテクノロジーズのSREチームでは、YAML形式のSLI/SLO仕様を一つのGitHubリポジトリに集約しています。SREチームはリポジトリにテンプレートを設け、開発チームはそのテンプレートに基づいてSLI/SLO仕様を定義し、それをコミットしプルリクエストを作成します。そしてSREチームはそのプルリクエストをレビューします。この流れで、SLI/SLO仕様の理解と監視への反映作業をスムーズに行うことができます。結果として管理コストが削減でき、KINTOテクノロジーズの開発組織全体であらゆるプロダクトのSLOが参照しやすくなります。 依存関係のサービスレベルは、自サービスのサービスレベルに大きく影響を与えます。KINTOテクノロジーズで開発されているサービスは、他のKINTOテクノロジーズ開発サービスとの依存関係にあるため、組織の垣根を越えてサービスレベルを共有することで、自サービスのサービスレベルを担保するのに役立ててもらいます。 LatencyのSLI 「遅さは新たなダウン」とも言われるため、5xx系エラーの有無だけでなく、レスポンスタイムについても監視が必要となります。 次の簡単なSLI/SLO仕様をSloth規格のYAMLファイルで表現してみます。 Category SLI SLO Latency アプリケーションで測定される成功したリクエストの内、actuator以外の全てのリクエストパスを統合して計測します。30日のリクエストのうち、3000ミリ秒以内にレスポンスを返す比率。 99% version: "prometheus/v1" service: "KINTO" labels: owner: "KINTO Technologies Corporation" repo: "slo-maintenance" tier: "2" slos: ... # We allow failing (less than 3000ms) and (5xx and 429) 990 request every 1000 requests (99%). - name: "kinto-requests-latency-99percent-3000ms" objective: 99 description: "Common SLO based on latency for HTTP request responses." sli: raw: # Get the average satisfaction ratio and rest 1 (max good) to get the error ratio. error_ratio_query: | 1 - ( sum(rate(http_server_requests_seconds_bucket{le="3",application="kinto",status!~"(5..|429)",uri!~".*actuator.*"}[{{.window}}])) / sum(rate(http_server_requests_seconds_count{application="kinto",status!~"(5..|429)",uri!~".*actuator.*"}[{{.window}}])) ) alerting: name: KINTOHighErrorRate labels: category: "latency" annotations: summary: "High error rate on 'kinto service' requests responses" page_alert: labels: severity: "critical" ticket_alert: labels: severity: "warning" その際、ヒストグラムでデータを取得するため、application.ymlに次の設定を追加します。 management: ... metrics: tags: application: ${spring.application.name} distribution: percentiles-histogram: http: server: requests: true slo: http: server: requests: 100ms, 500ms, 3000ms management.metrics.distribution 以下の設定を追加することにより、summary型の percentiles ではなく、histogram型の percentiles-histogram でメトリクスを取得するように設定しています。この理由として、 percentiles は特定のパーセンタイルのレスポンスタイムをタスク単位で直近1分間のみ集計するので複数タスクでの集計ができず、30日間といった任意の範囲での集計もできないためです。これに対し、 percentiles-histogram は閾値以内のレスポンスタイムだったリクエスト件数を値として保持するため、PromQLを用いて複数タスクを横断した任意の範囲での集計が可能です。 そうすることで、リクエスト件数と総件数との比率を用いて、LatencyのSLI仕様を表現しています。 議論 設定可能なSLOは94%以上推奨 サイトリライアビリティワークブックでは、エラーバジェットの消費率を検知したい場合に推奨されるウィンドウとバーンレートの閾値が定められています。 https://sre.google/workbook/alerting-on-slos/#6-multiwindow-multi-burn-rate-alerts Slothは、デフォルトでサイトリライアビリティワークブックに記載の複数のバーンレートに対応しています。よって、アラートが発生する最大バーンレートの閾値は14.4となります。 その場合、例えば、SLOが93%の時のエラーバジェットは7%になります。ここで試しに、バーンレートが14.4のときのエラーレートを計算してみると、14.4 * 7 = 100.8 になります。基本的にエラーレートは、「エラーリクエスト/全てのリクエスト」によって算出されるため、エラーレートが100を超えることはありません。従って、SLOを93%と設定したときにバーンレートが14.4を超える事態を報告するアラートが発砲される可能性は0となります。 そのため、設定するSLOは94%以上を推奨しています。 おわりに ここまで、私たちKINTOテクノロジーズのSREチームの取り組みについて紹介してきました。いかがでしたでしょうか。現状、組織全体として、非常に厳格なサービスレベル管理が求められているわけではありませんが、今回紹介した手法を用いて、手軽に有益なアラートを試せることに満足しています。 Platform Groupでは一緒に働ける仲間を募集しています。少しでも興味を持ったり話を聞いてみたいと思った方は、お気軽にご連絡ください! @ card
アバター
こんにちは KINTOテクノロジーズの小山です。モバイルアプリの開発・運用に携わっています。担当はiOSです。 前回はiOS開発に特化した記事( Combineを使ってMVVMを実現した話 )を書かせてもらいましたが、今回はその開発現場で取り入れているアジャイル開発の内容です。 以前から私の携わっているモバイルアプリ開発ではアジャイル開発を取り入れており、スクラムを使って運用しています。今回、スクラムマスターをやってみないかとの話をいただきましたので、その経過を紹介したいと思います。 ※この記事は「アジャイル」をテーマにした一連の連載記事となります。 私たちは「組織としてアジャイルな状態」になっていくために、 さまざまな課題や困難に立ち向かってきました。 時には失敗もありましたが、それでも着実に成長を続けています。 この連載記事では、そんな私たちの実際の取り組みをご紹介していきたいと思います。 なぜスクラムマスター研修を受けたのか 以前からスクラムを使った開発には携わっていましたが、いずれも開発者の立場での参画でした。スクラムマスターとしての立ち回りはやったことがなかったため不安に感じていたところ、スクラムマスター研修なるものが存在するとの情報を得て受講に至りました。 今回は株式会社アトラクタさんが主催している認定スクラムマスター研修というものを受講しました。 それまでの開発チーム 私の所属している開発チームについて紹介したいと思います。 体制・役割 プロダクトオーナー チームリーダー バックエンド開発チーム フロントエンド開発チーム iOS開発チーム ←今ここ Android開発チーム QA デザイナー モバイルアプリの開発現場ではこのようにフロントエンドの開発チームが2チームに分かれることがままあります。 今回私がスクラムマスターを実施するということで、以下のようになる想定でした。 プロダクトオーナー バックエンド開発チーム スクラムマスター(チームリーダー) フロントエンド開発チーム スクラムマスター ←ここ半分 iOS開発チーム ←ここ半分 Android開発チーム QA デザイナー 元々チームリーダーはバックエンド開発寄りの立場にいたため、そちらのスクラムマスターを担当し、フロントエンド側を私が担当する想定でした。 …今思えば奇妙な構成を考えていたと感じますが、当時の私は気づけず、気づいていたとしても自信を持って意見できる状態ではありませんでした。 既存のチーム開発における課題 もちろんそれまでもアジャイル開発を進めていたため、スクラムを利用する上での疑問点や課題が多くありました。 開発メンバーが10人を超えてしまっているがどうすれば良いか 各種スプリントイベントをバックエンドとフロントエンドで分けて実施しているが問題ないのか スクラムへの理解度がメンバーによってばらつきがある 等々… これらが解決できればと思い研修に臨みました。 いざ、受講! 研修概要 スクラムマスター研修は3日間のカリキュラムとなっており、毎日13時〜18時の間オンラインで実施されました。その後任意のタイミングで試験を受けることができ、合格するとScrum Allianceが発行するCertified ScrumMaster(認定スクラムマスター)の資格を取得することができます。 3日間の内訳はざっくり以下の通りでした。 1日目:アジャイルとスクラムについての座学 2日目:スクラムの実践 3日目:スクラムマスターの座学と実践 アジャイルやスクラムについては書籍やWebなどから学ぶことができますが、それらを遥かに凌ぐわかりやすさで座学が進められました。忘れかけていた原則やマインドを学び直した上で、疑問点は常に講師の方に聞くことができ、実践形式で知識を定着させることができました。とても有意義な時間でした。 研修を受けて変わったこと とにかく、 自信がついた これに尽きます。 スクラムマスターをやる上で絶対に必要なのがスクラムについて指導をすることです。 それまでの学びでは「こういうことが本には書いてあるけど実際こうだしな」と自分でも疑問を解消しきれずにいました。原理原則は知っていても自信を持ってチームメンバーに広めることができずにいました。 研修を受けることで少なくとも自分が思っている疑問点は全て解消でき、スクラムの書籍も出しているような著名な講師の方に教えてもらったという絶対的な安心感が自信に繋がりました。また認定スクラムマスターの資格も手に入れることができたためこれも自信の裏付けとなりました。 受講後、やってみた プロダクトオーナーの理解を得る 私のチームでは研修の中でアンチパターンと呼ばれる手法をいくつも取っていることが判明しました。そこでまずプロダクトオーナーにアンチパターンがあること、なぜアンチパターンなのか、を説明しました。 一気に全てを変えることは難しいのでわかりやすいところから変えていこうとなり、私のチームでは「バックログの運用方法」から変えることを検討していくことになりました。 チームメンバーに研修の展開 アジャイル開発ではチーム全体で同じ意識を持つ必要があります。これがアジャイルを進める上でのとても重要なことだと私は思います。「バックログの運用方法」から変えるにしても、まずは意識を統一させることが必要でした。 そのために、受講後の週で内部展開用の資料を新たに作成し集合研修を行いました。まさに自信がついたからこそできた行動でした。 チームメンバーからは「アンチパターンを実施してしまっていたことがわかった」という声や、「研修受講後すぐに展開してくれるのが嬉しい!」といった反応をいただけました。正直なところ資料の作成と研修展開はかなり大変でしたが、やってよかったです。 改善内容の実践 「バックログの運用方法」の改善にあたり、現状の課題点とあるべき姿を説明し、次のバックログリファインメントから取り入れていきました。ここまでを研修受講後から1週間というスピードで実施しました。研修受講したてのホットな状態でここまでやり切れたのは、理解のあるプロダクトオーナーと開発メンバーのおかげでした。 体制はどうなった? 研修内容のチームへのフィードバックは滞りなく完了したのですが、ここで問題が発生してしまいました。 なんとチームリーダーが急遽育休に入るということでした。 当社は育休制度(もちろん男性も)がありますし、育児を応援したい気持ちはチーム全体としてありましたので、気持ちよく送り出すことにはなったのですが、 どうやらこのビッグ(メンバー数が)プロジェクトを私がスクラムマスターとしてサポートする必要 がありそうです。ひええ。 ということで再構成された体制が以下の通りです。 プロダクトオーナー スクラムマスター ←ここ半分 バックエンド開発チーム フロントエンド開発チーム iOS開発チーム ←ここ半分 Android開発チーム QA デザイナー 前述の …今思えば奇妙な構成を考えていたと感じますが、 の記載については、そもそもチーム内にスクラムマスターは1名で十分なためです。主にバックエンドをフォローするスクラムマスター、主にフロントエンドをフォローするスクラムマスター、といった分け方は本来の役割を期待されているようには見えません。名ばかりのスクラムマスターで、チームリーダーのような役割になってしまいそうな懸念があります。 また、元々実施していたチーム分け(バックエンド/フロントエンド)ではどうしてもチーム間でのやり取りが発生し、結局ブリッジとなる役割のメンバーが必要になってくる課題がありました。かつそのブリッジメンバーの負担が非常に大きくなってしまう傾向があったので、このタイミングで1チームにまとめられたことは今後のこと(後述)を考えるとむしろ好手であったと私は考えています。 これからやっていきたいこと 当面の目標としては、上記の改善を皮切りにどんどん開発チームをアップデートしていくことです。上手にスクラムが回るようになってきたら、今度はフィーチャーチームに分割するといったことにもトライをしていきたいと考えています。こうすることで当初考えていたチームの課題を全て解決することができます。 また長期的には社内にいるスクラムマスターと協力して、スクラムを使ったアジャイル開発を広めていけたらと考えています。うまく走り出すところまでサポートしたら、またさらに別のチームをサポートし、そうしてスクラムがもっと広まればより良いプロダクトの開発に貢献できるはずです。 終わりに 以上、スクラムマスター研修の体験記でした。とにかくメリットだらけですので、もし読んでいる方の中にスクラムがなかなかうまくいかないと悩んでいる方がいれば、受講を検討してみてはいかがでしょうか。受講料が少々お高めなので個人では受けづらいですが、会社に費用を負担してもらうために会社・上長を説得できる要素は十二分にあると思います。 個人的にスクラムを使ったアジャイル開発の最大のポイントは「チームごとにプラクティスが違う」というところだと思います。他社のプラクティスが必ずしも自社に適用できるとは限りません。いろいろなやり方を模索していくのは難しいけれど実は一番楽しいポイントかもしれませんね。
アバター