TECH PLAY

KINTOテクノロジーズ

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

936

この記事は KINTOテクノロジーズアドベントカレンダー2024 の15日目の記事です🎅🎄 学びの道の駅の中西です。今年は学びの道の駅プロジェクトが立ち上がり組織化されました。そんな中、社内Podcastの運営も行っており、今年のアドベントカレンダーではその内容もお届けしたいと思います。 「学びの道の駅」とは? 「学びの道の駅」は、社内で頻繁に開催される勉強会をもっと便利に、そして効果的に活用するために立ち上げられたプロジェクトです。社内の有志が中心となり勉強会の開催を支援し社内の知識共有を促進することを目的としています。 Osaka Tech Lab情報共有会 KTC学びの道の駅ポッドキャストでは、社内の勉強会を開催している方にインタビューを行っています。その名も「突撃!となりの勉強会」。本日のポッドキャストのゲストは「Osaka Tech Lab情報共有会」のご担当、沖田さんと福田さんにお越しいただきました。早速ですが、お二人の自己紹介をお願いできますか? インタビュー 沖田さん: はい、沖田と申します。モバイルアプリ開発グループに所属しており、開発PMとして他のグループとモバイル開発チームをつなぐ役割を担っています。よろしくお願いします。 福田さん: 福田です。2020年7月にKTC(当時は株式会社KINTO)に入社し、プロデュースグループで業務を行っていました。途中、10ヶ月の育児休業を取りまして、2024年2月に復帰しました。現在はクリエイティブ室に異動し、KTCのコーポレートサイトの運用とリニューアルのディレクションを担当しています。よろしくお願いします。 HOKAさん: ありがとうございます。では、大阪の情報共有会を開催したきっかけについて教えてください。 福田さん: Osaka Tech Labは2022年4月に立ち上がりました。当初は分析グループのトモナガさんお1人でした。メンバーが増えるにつれて、「隣の人がどんな仕事をしているのかわからないね」という話題が上がるようになりました。そこで、コミュニケーションを活性化するために情報共有会を始めました。 沖田さん: Osaka Tech Lab情報共有会の創設者は、福田さんですよね。 最初は自己紹介から始まり、趣味を共有することで共通点を見つけて仲良くなることを目指していました。 HOKAさん: なるほど。沖田さんも第1回から参加し、推進されていったんですね。どのように変わっていったのでしょうか? 沖田さん: 最初は人数も少なかったので、みんなで集まってやろうという感じでした。今では17回目を迎え、メンバーが増えてきたことで、会の形式も少しずつ変化してきました。 HOKAさん: Osaka Tech Labが主役の会というのはどういう意味でしょうか? 沖田さん: Osaka Tech Labのコミュニケーションを活性化することが目的です。メンバー同士の仕事内容や取り組みを共有し、横のつながりを強化しています。また、テックブログでのアウトプットにもつなげています。 HOKAさん: 素晴らしいですね。実際に形になっていることを感じます。参加者の反応や雰囲気の変化もありますか? 福田さん: 最初は会話が多かったですが、今では課題を話し合う場として機能しています。オフィスをより良くするための話し合いも行っています。 沖田さん: 例えば、オフィスに時計がなかったので設置したり、本棚を増設したりしました。みんなで意見を出し合い、改善を進めています。 HOKAさん: 今後の展望について教えてください。 沖田さん: 和気あいあいとした雰囲気を保ちながら、組織が大きくなってもコミュニケーションを大切にしたいです。 福田さん: 大阪からKTCを盛り上げていきたいと思っています。情報共有会を継続するか、新しいスタイルで続けていくか、考えていきたいです。 HOKAさん: 他の部署の方が参加したい場合はどうすればいいでしょうか? 沖田さん: 毎月LT枠の募集をしているので、ぜひ参加していただきたいです。 Osaka Tech Lab メンバーにお気軽にお声掛けください。 福田さん: 情報共有会の後にはビアバッシュも開催しているので、コミュニケーションの機会としても活用していただければと思います。 HOKAさん: 最後に、聞いている皆さんに一言お願いします。 沖田さん: ぜひ大阪に来てください。お待ちしています。 福田さん: 情報共有会にLTを持って参加してください。お待ちしています。 HOKAさん: 今日はありがとうございました。大阪からの取り組みが全体に波及していく様子を感じました。沖田さん、福田さん、ありがとうございました。 以上がインタビュー記事のまとめです。Osaka Tech Lab情報共有会の魅力や役割について、読者に伝わる内容になっていますね。 今回はOsaka Tech Lab情報共有会の詳細と、その運営の背景、今後の展望についてお届けしました。次回の勉強会も楽しみにしてください!
アバター
はじめに こんにちは! KINTOテクノロジーズの新車サブスク開発グループに所属している劉(ユ)です。 完璧ではないかもしれませんが、少しずつ問題をより良い方向に改善していくことを目指して日々努力しています。 この記事ではSpring BootにおいてRedisのPub/Subを導入してシステム日付を変更する内容について共有したいと思います。 導入に至った背景 システムのQAやテストを実施する際、システムの日付を変更しないと確認できないケースが多くあります。 特に、サブスクリプションサービスにおいては特定の日付に依存するビジネスロジックをテストする必要があります。 例えば、期間開始日や期間終了日、月額料金、中途解約の精算金、メンテナンス点検・車検などに基づいた処理の検証が求められます。 これまではシステム日付が環境設定ファイルで定義されていたため、日付を変更するたびにコンテナの再デプロイが必要でした。 その結果、テストやQAを行うたびに再デプロイに5分以上の時間がかかるという問題が発生していました。 このような状況における課題を解決した内容を紹介したいと思います。 これを入れたことでどういうメリットがあったか? RedisのPub/Subの概念を導入することで、テスト環境でのシステム日付変更がより効率的になり、迅速な対応が可能となりました。 これにより、テストやQAなどの工数を削減できるようになり、作業効率が向上しました。 具体的にコンテナの再デプロイは不要になり、変更したい設定項目(トピック)に対するメッセージ(変更したい設定値)を発信するだけで、各コンテナはリアルタイムで変更内容を受信し、設定値の変更ができるようになりました。 また、複数のコンテナが構成されている場合でも、すべてのSubscriberがメッセージを受信しているため、複数のコンテナでも再起動せずに設定値を変更できます。 さらに、システム日付変更のログも出力できるため、変更履歴を追跡することが可能となりました。 他にもSpring BootのProfileの設定で、指定したテスト環境のみでこの機能を有効にし、本番環境や他の環境へ誤って適用することを防げます。 ※Profileについては こちら Redis Pub/Subとは Redis Pub/Subは、メッセージキューのメッセージングパターンの1つです。 メッセージキューとは、サーバーレスやマイクロサービスアーキテクチャにおける非同期通信の手法の1つで、分散システムにおいてリアルタイムのイベント通知を実現します。 この仕組みは、異なるソフトウェアモジュール間で拡張かつ安定した通信をサポートするため、データベースやキャッシュとしての利用に加え、メッセージブローカーとしても広く使用されています。 主な構成 Topic(主題):購読する対象となる主題やテーマです。 Publisher(発行者):特定のTopicに関するメッセージを発信します。 Subscriber(受信者):購読したTopicに対して、発行者のメッセージを受信します。 Keyspace Notifications 何らかの方法でRedisデータセットに影響を与えるイベントを受信し、Redisキーおよび値の変更内容をリアルタイムでモニタリングします。 ではどういう実装か? システム日付変更の仕組み トピックに対するメッセージを送信するPublisherとしてAPIを実装しました。 購読しているトピック(キー)に対するイベントが発生すると複数のコンテナ(Subscriber)がメッセージを受信し、リアルタイムで設定値を変更します。 システム構成 JavaとSpring Bootを使用して構築されています。アプリケーションはコンテナ化され、クラウド環境で稼働しています。 build.gradleにlibraryの追加 implementation 'org.springframework.data:spring-data-redis' RedisConfigクラスの実装 @AllArgsConstructor @Configuration public class RedisTemplateConfig { private final RedissonClient redissonClient; @Bean public RedisTemplate<String, String> redisTemplate() { RedisTemplate<String, String> template = new RedisTemplate<>(); template.setConnectionFactory(new RedissonConnectionFactory(redissonClient)); template.setDefaultSerializer(new StringRedisSerializer()); return template; } @Bean public RedisMessageListenerContainer redisContainer() { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(new RedissonConnectionFactory(redissonClient)); return container; } } Publisherの実装 トピックに対するメッセージを送信するAPIを実装します。 @RestController public class SystemTimeController { private final SomeService service; @PostMapping("/update") public void updateSystemTime(@RequestParam String specifiedDateTime) { service.publish(specifiedDateTime); } } @Service @RequiredArgsConstructor public class SomeService { private final RedisTemplate<String, String> redisTemplate; // Topicのキーを定義 private static final String FOO_TOPIC = "foo-key"; public void publish(String specifiedDateTime) { // Topicに対するメッセージを送信する redisTemplate.opsForValue().set(FOO_TOPIC, specifiedDateTime); } } Subscriberの実装 購読しているトピック(キー)に対するイベントが発生すると、メッセージを受信します。 @Slf4j @Component @Profile("develop1, develop2") // 指定されたテスト環境のプロファイルのみ有効 public class FooKeyspaceEventMessageListener extends KeyspaceEventMessageListener { private final RedisMessageListenerContainer listenerContainer; private final RedisTemplate<String, String> redisTemplate; private static final String FOO_TOPIC = "foo-key"; @Override public void init() { doRegister(listenerContainer); } public FooKeyspaceEventMessageListener( RedisMessageListenerContainer listenerContainer, RedisTemplate<String, String> redisTemplate) { super(listenerContainer); this.listenerContainer = listenerContainer; this.redisTemplate = redisTemplate; } @Override protected void doHandleMessage(Message message) { // Redisからシステム日付を取得する String systemTime = updateSystemTimeConfig(redisTemplate.opsForValue().get(FOO_TOPIC)); // システム日付を反映するメソッドを作成してコールする updateSystemTimeConfig(systemTime); log.info("Receive a message about FOO_TOPIC: {}", message); } } さいごに 今回の記事を最後までお読みいただき、ありがとうございます。 まだまだ至らない点が多いですが、毎回少しずつ問題を改善しながら成長していこうと努力しています。 完璧な構造や実装ではありませんが、少しずつより良い方向へ進んでいくことが重要だと思っています。 今後もこうした小さな進歩が集まり、より良い結果を生み出せるよう、引き続き学び続けていきます。 共に成長する旅路を歩んでいければと思います。ありがとうございました。
アバター
This article is part of day 6 of KINTO Technologies Advent Calendar 2024 . 🎅🎄 Introduction Hello, I'm Tada from the SCoE Group at KINTO Technologies (from now on referred to as, KTC). SCoE is an abbreviation for Security Center of Excellence, and some of you may not be very familiar with the term yet. At KTC, we restructured our CCoE team into the SCoE Group this past April. The story behind the reorganization is summarized in this blog article , so please do check it out. Also, I've written this blog article about the Osaka Tech Lab (our company's Osaka office), so feel free to check it out as well. KTC operates many production environments on Amazon Web Services (hereafter referred to as AWS), and with the leveraging of OpenAI, the use of Microsoft Azure (hereafter referred to as Azure) has been on the increase as well lately. One of the tasks of the SCoE is to provide an environment with preconfigured security settings based on group policies. In this blog, I would like to introduce some of the security settings we implement when providing an Azure subscription. Since Azure-specific terms will be used, please refer to the official website and other resources for more details. Designing the Azure Landing Zone and Management Groups When considering security settings, it is important to first understand landing zones and management groups. KTC’s subscription environment has been designed and constructed based on Azure landing zone design principles. However, rather than use Microsoft’s official landing zone as is, we have designed a lighter one of our own to go with KTC’s environment, referring to best practices as we did so. Within the landing zone, we have designed several management groups to organize subscriptions logically and manage them efficiently. The following figure gives an overview of this. By using these management groups, we apply appropriate policies to each subscription. Management group Overview KTC The root of the management groups, used to apply the policies that will be common to all of them Management For managing subscriptions used for security, such as ones used for consolidating the Activity Logs of all subscriptions Workload For managing subscriptions used for workloads Sandbox For managing subscriptions used for sandboxes PolicyStaging For managing subscriptions and management groups used for testing Azure policies The key point is that we have one, unified management group for workloads. This management group contains subscriptions used for products, and within a single subscription, has separate production, development, and staging environments on a per-resource-group basis. There are various approaches to environment separation, but at KTC, we started with this approach because the number of workloads is not large, it is limited to specific Azure services, and cost management at the subscription level is easier. We are also thinking of reviewing it if use of Azure increases in the future. Role of the “Management” Management Group The Management management group is a management group used to consolidate subscriptions for operational management and the deployment of security tools common to all subscriptions. Only members responsible for operations and monitoring have access, and for example, we manage a subscription here that aggregates and monitors the Activity Log for all subscriptions. Configuring Security Settings Using Azure Policies By using Azure policies , it is possible to create resources in accordance with security and governance requirements, and violations can be detected and remediated. We use Azure policies to automatically apply security settings when creating subscriptions here at KTC, too. We are only using built-in policies at the moment, and not yet gone as far as creating custom ones. I would like to consider doing that in the future if the environment changes (due to an increase in workloads, for example). The following is an example of a typical setup that utilizes Azure policies. Monitoring and storing Activity Logs Configuring and using Defender for CSPM At KTC, we do not adopt an approach of overly applying Azure policies as preventive guardrails. This decision is based on factors such as the relatively small number of workloads at KTC, the skill level of engineers, and operational costs. Rather than increasing the constraints with strict preventive guardrails, our approach is all about leaving the engineers a certain degree of freedom, and conducting kaizen (continuous improvement) to address issues detected through heuristic guardrails. The intention behind this is to enable our engineers to hone their skills through problem solving, get increasingly interested in the work, and grow. Monitoring and storing Activity Logs The Activity Logs from all the subscriptions are consolidated in the Log Analytics workspace of the Management subscription. We have set things up so that the Audit Logs get automatically consolidated by means of an Azure policy when new subscriptions are added, too. The Azure policy we are using is shown below. Configure the Azure Activity Logs to stream to the specified Log Analytics workspace Since Log Analytics has a default retention period of 90 days, we keep backups in a storage account. However, there is no setting for this in the Azure policy, so we are doing it manually. We have confirmed that it can be configured to happen automatically by creating a custom policy, but have not gone as far as doing it yet. Configuring and using Defender for CSPM These are called heuristic guardrails. If any risky configurations or actions are performed in the Azure environment, we use Cloud Security Posture Management (CSPM) solutions to detect these risks. In the case of Azure, Microsoft Defender for Cloud can be used for CSPM. Microsoft Defender for Cloud is a solution for Cloud Native Application Protection Platform (CNAPP), covering security solutions such as CSPM (Cloud Security Posture Management) and CWPP (Cloud Workload Protection Platform). Microsoft Defender for Cloud’s CSPM features include Foundational CSPM, which is free, and Defender CSPM, with which resources like servers, databases, and storage give rise to costs. In KTC’s case, we use Defender CSPM, because it enable you to do more detailed CSPM checking. Using the following Azure policy, we configure Defender CSPM automatically upon issuing subscriptions. Configure things so that Microsoft Defender CSPM will be enabled Once we have configured the settings, we periodically monitor the alert situation via Microsoft Defender for Cloud, and if there are any risky configurations, we work with the product-side staff using the subscription to do kaizen to address the risks. We are not implementing cloud workload protection at the moment, but would like to consider it in the future (as resources increase, for example). Threat Detection In order to discover security incidents and unauthorized access in the Azure environment at an early stage, we have introduced a threat detection mechanism. I think many companies that have introduced AWS are using Amazon GuardDuty to achieve this, and that is also what KTC is doing. For Azure, using Microsoft Sentinel is apparently the surefire approach, but given KTC’s environment, in view of the cost and effort involved in introducing that, we are achieving it through the CDR (cloud detection response) features of the third-party product sysdig instead. The CDR is actually handled by the OSS Falco . Falco detects and notifies you of abnormal behavior, potential security threats, and other violations regarding your hosts, containers, Kubernetes, and cloud environment as a whole. General threat detection rules are provided, and these can also be customized and tuned. This makes it very easy to use. KTC was already using sysdig for CSPM and threat detection for its Google Cloud environments, so we are applying the know-how from that to Azure as well. Summary In this article, I talked about some of the security settings we employ at KTC when we provide Azure subscriptions. To enhance security, we utilize Azure Policy, Microsoft Defender for Cloud, and Sysdig's CDR functionality. As I said in “Configuring Security Settings Using Azure Policies,” I think how strict you should make preventive guardrails will depend largely on your own company’s situation, so you should design and operate things in the best ways to suit that. I hope this content serves as a helpful reference for security settings when using Azure. Thank you for reading all the way to the end. Conclusion The SCoE Group is looking for new team members to work with us. We welcome those with practical experience in cloud security as well as those who are interested but have no prior experience. Please feel free to contact us. For additional details, please check here.
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の23日目の記事です🎅🎄 はじめに こんにちは!KINTO テクノロジーズでiOSアプリケーションを開発しているFelixです。今日は、最近見つけた設計ツールを紹介します: Play .開発の生産性を向上させる方法を模索する中で、UI設計をSwiftコードに変換できるツールを探していました。そんな時にPlayを見つけ、予想以上に多くの機能を持っていることがわかりました。Playを使うと、開発者や設計者は、SwiftUI コードを自動的に生成しながら、デバイス上で直接ユーザーインターフェースを作成、テスト、反復処理できます。このツールは、設計と開発を結び付け、より優れたコラボレーションを促進し、効率を高めます。 この投稿では、私が特に便利だと感じた機能をいくつか紹介します。 設計者はNative SwiftUIコンポーネントを使用できます KINTOでは、設計者と密に連携して、アプリケーションがAppleのヒューマンインターフェースガイドラインに沿うようにしています。ただし、これは難しい場合があります。設計者はiOSにおける詳細な実装を必ずしも完全に把握しているわけではないからです。Playの場合、設計者はネイティブ SwiftUI コンポーネントを使用して設計することができます。これにより、設計者はiOSですぐに利用できる機能をより深く理解できるようになり、開発者はiOSネイティブ ライブラリにすでに含まれている機能を再発明する必要がなくなります。このツールは、ネイティブコンポーネントを活用することで、設計者と開発者とのコミュニケーションを明確にし、Appleのヒューマンインターフェイスガイドラインの遵守を確保します。 相互作用の定義 UI設計のみに焦点を当てている多くの設計ツールとは異なり、Playを使用すると、 設計者はアクションとアニメーションをiOSで自然に設定することができます。これにより、コンポーネントがどのように動作するかがより明確になります。例えば、設計者は、ボタンを押すと何が起こるか、次のページへの移行がどのように見えるか、アニメーションがどのように流れるかを指定できます。この機能は、設計と開発のギャップを埋め、コンセプトから実装への移行を円滑にします。 ライブプレビュー 私たちの開発プロセスで特に時間のかかる部分は、UI設計を実装し、テストビルドの公開後にフィードバックを待つことです。Playを使用すると、このプロセスが円滑化し、待ち時間が大幅に短縮します。設計者がライブプレビューで動作を確認できるからです。インタラクションが設計内で設定されると、Playを使用することで、設計者は実際のデバイスで直接作業をテストできます。このツールはiOSのネイティブライブラリを使用してデモアプリを構築するため、設計者は製品版で期待される正確な動作を体験できます。この機能は、設計やインタラクションを開発者に引き渡す前に検証する際に非常に役立ちます。 他にも... もちろん、PlayはUI設計をSwiftUIコードに変換しますが、それだけでなく、ワークフローを向上させる追加機能も提供します。例えば、Playは既存のFigma設計のインポートをサポートしているため、移行が簡単になります。設計者は、UI がさまざまな画面サイズやデバイスでどのように表示されるかをプレビューできるため、レスポンシブで適応可能な設計を確保できます。まだ私が発見していない機能がたくさんあると思います!私たちのチームはこのツールの使用に非常に興味を持っていますが、これを試す前にiOSとAndroid用に設計を分ける必要があります。iOS用の独立した設計があり、チームの生産性を向上させ、設計者と開発者とのコミュニケーションを促進したいと考えている場合は、Playを試してみることを強くお勧めします。
アバター
はじめに こんにちは、KINTO テクノロジーズ ( 以下、KTC ) SCoE グループの桑原 @Osaka Tech Lab です。 SCoE は、Security Center of Excellence の略語で、まだ馴染みのない言葉かもしれません。KTC では、2024 年 4 月に CCoE チームを SCoE グループとして再編しました。SCoE グループについて知りたい方は、 クラウドセキュリティの進化をリードする SCoE グループ を参照ください。 また、KTC の関西拠点である Osaka Tech Lab については、 Osaka Tech Lab 紹介 をご参照ください。 SCoE グループでは、AWS や Google Cloud , Azure のクラウドにおける「ガードレール監視と改善活動をリアルタイムで実施する」をミッションに活動しています。具体的な活動の観点としては以下の 3 つです。 セキュリティリスクを発生させない セキュリティリスクを常に監視・分析する セキュリティリスクが発生したときに速やかに対応する 今回は「KTC のクラウドセキュリティエンジニアって何をやってるの?」を具体的に知っていただこうと思います。 クラウドセキュリティエンジニアのとある一日 具体的なイメージをしていただくために、クラウドセキュリティエンジニアの "とある一日" をご紹介します。(セキュリティという分野の都合上、詳細に語れない部分があることをご了解ください。) アラート確認 朝一番に行うのは、リスクの高いアラートが発生していないかの確認です。CSPM(Cloud Security Posture Management)や脅威検知サービスを使用して、クラウド環境全体のセキュリティ状況を把握し、即時対応が必要なアラートがないか確認します。 KTC では、CSPM や脅威検知サービスとして、 AWS Security Hub や Amazon GuardDuty 、 Sysdig Secure 等のサービスを活用しています。 アラートを確認する際は、以下の観点で対応します。 アラートの優先順位付け : 重大度や影響範囲に基づいてアラートを分類し、優先順位を付けます。 アラートのトリアージ : 発生したアラートの原因を特定し、必要な対応策を講じます。 偽陽性(過検知)の管理 : セキュリティツールは時折、偽陽性(False Positives)を発生させることがあります。これにより、実際には問題のないアクティビティがアラートとして報告されることがあります。これらの管理もアラート処理として対応します。 業務上必要なオペレーションの識別 : 偽陽性の管理に関係しますが、一部のアラートは、業務上必要なオペレーションによって引き起こされることがあります。例えば、各プロダクトの担当者が定期的に行うメンテナンス作業などです。これらのアクティビティを識別し、適切に対応します。 これにより、クラウド環境全体のセキュリティ状況を把握し、即時対応が必要なアラートがないか確認します。 情報収集、キャッチアップ 次に、サイバーセキュリティの動向や AWS などのクラウドサービスの最新情報をキャッチアップします。情報源としては以下のものを利用します。 X(旧:Twitter) : サイバーセキュリティの専門家や業界リーダーをフォローしています。彼らは最新の脅威情報や対策を共有しているため、リアルタイムでの情報収集が可能です。 AWS や Google Cloud の公式ニュースやブログ : クラウドサービスプロバイダーの公式情報は、新機能のリリースやセキュリティアップデートに関する重要な情報源です。これにより、新サービスのローンチ情報や最新の技術動向、ベストプラクティスを把握できます。 その他ニュースサイト : サイバーセキュリティに特化したニュースサイトやブログを定期的にチェックすることで、業界全体の動向を把握し、最新の脅威や攻撃手法についてキャッチアップします。 SIEMでの脅威検知 KTC では、SIEM(Security Information and Event Management)として、 Splunk Cloud Platform を使用しています。この Splunk にセキュリティ関連ログを集約し、ログの横断分析と監視を行える環境を整備しています。 この日は、Splunk のダッシュボードにて、違和感のあるログを発見しました。内容としては、「Google Cloud の組織ポリシーで制限されているサービスに対して、リソース作成を何度しようとしてオペレーションに失敗している」というものです。 Splunk にて独自に作成している Google Cloud Audit logs 用のダッシュボードの情報から、おおよそのアクティビティは判断できていましたが、より詳細に調査します。 まず、Google Cloud の組織ポリシーで制限をかけているサービスに対して、リソース作成を何度もリトライしているユーザーを特定します。 Google Cloud の Audit log (audit:policy_denied) ではユーザー情報はマスクされた状態でログ出力されるため、このログ単体ではユーザーは特定できません。端末系のログなどと一緒に横断的なログを分析することで、ユーザーを特定します。この分析に使用するクエリを作成し、該当のユーザーを特定しました。 次に特定したユーザーの行動を詳細に分析するためのクエリを作成し、ログを分析します。 どうやら、AI/ML のサービスである Vertex AI を使用しようとしている模様です。該当のプロジェクトでは、Compute 系の利用申請は出ていませんでしたので、組織ポリシーにて Compute 系サービスの使用を制限をしています。 Vertex AI は、Notebook を使用する際に、Compute Engine (GCE) インスタンスが起動します。よって、この部分で組織ポリシー違反となります。 結果的に、「Google Cloud プロジェクト新規発行申請時に利用予定サービスの記載漏れがあった」ということで、この件は、問題ないアクティビティであることを確認しました。 クラウドベンダーネイティブのセキュリティサービスに対するコスト最適化検討 クラウドベンダーが提供するセキュリティサービスは従量課金制であり、クラウドリソースが増加すると、それに伴ってセキュリティサービスの利用料も増加します。 私たちが考える「セキュリティ」は、 「ビジネスのためのセキュリティ」であり、「ビジネスを阻害するセキュリティ」は NG です。 そのため、「セキュリティとコストのバランス」も重要なポイントであり、セキュリティサービスのコスト最適化も SCoE グループのミッションに含まれます。 この日は、全体コストの中で割合が高いいくつかのセキュリティサービスについて、コスト最適化の可能性を調査しました。 上記のグラフは、今回の分析対象となったサービスを示しています。その中でも特に AWS Config に注目しました。(具体的な項目名はマスキングしています) AWS Config は、AWS リソースの構成を監査、評価、そして記録するためのサービスです。2023 年 11 月までは、AWS Config の記録方式として「リソース構成の変更が発生するたびに記録する方式」しか提供されていませんでした。この方式は、「記録頻度:継続的な記録」と呼ばれています。 つまり、リソースの変更頻度が高い場合、AWS Config の記録回数が増加し、それに比例して利用料金も増加する仕組みです。 例として、ネットワーク関連のイベントを確認してみましょう。以下のデータは、ある AWS アカウントにおける 1 週間分の VPC およびネットワーク関連の構成変更回数を示したグラフです。 Elastic Network Interface (ENI) の作成・削除に該当する  CreateNetworkInterface と DeleteNetworkInterface が一日あたり約 17,000 件発生していることがわかります。 KTC では、Amazon Elastic Container Service (ECS) の Fargate を活用しています。このため、ECS タスク (コンテナ) が起動・停止するたびに、ENI の作成・削除が発生します。このような状況下で AWS Config を「記録頻度:継続的な記録」に設定している場合、これらの変更に伴う AWS Config の記録回数が膨大になり、それに応じて課金額も増加します。 しかし、2023 年 11 月以降、AWS Config に「記録頻度:日次記録」を選択できる機能が追加されました。この新機能により、リソースタイプごとに記録頻度を調整することが可能となり、セキュリティとコストのバランスを柔軟に取ることができるようになりました。一般的には、この設定を活用することで AWS Config の利用コストを最適化できると考えられています。 ただし、これは AWS Control Tower を使用していない場合に限ります。AWS Control Tower は複数の AWS アカウントのガバナンスを一元管理するためのサービスです。 AWS Control Tower を利用して AWS アカウントの AWS Config を管理している場合は、 Guidance for creating and modifying AWS Control Tower resources を確認してください。 ガイダンスの冒頭に記載されている以下の一文に注目してください。 Do not modify or delete any resources created by AWS Control Tower, including resources in the management account, in the shared accounts, and in member accounts. If you modify these resources, you may be required to update your landing zone or re-register an OU, and modification can result in inaccurate compliance reporting. この記載が示すように、 AWS Control Tower によって作成されたリソースを AWS Control Tower 以外の方法で変更または削除することは非推奨 です。 具体的には、2024 年 12 月現在、AWS Control Tower では AWS Config の記録頻度を変更する機能が提供されていません。そのため、AWS Control Tower 管理下の AWS Config の記録頻度を変更することは非推奨とされており、公式ドキュメントにも問題が発生する可能性があると記されています。 公式ドキュメントの内容を踏まえつつ、念のため AWS サポートにも問い合わせを行ったところ、同様の見解を得ました。 このように、「設定そのものは可能であっても、問題が発生するリスクがある、または非推奨とされる」場合には、安定したクラウドセキュリティとガバナンスを維持することが難しくなります。その結果、 「ビジネスを阻害するセキュリティ」 となりかねません。 以上を踏まえ、AWS Config の記録頻度変更は現時点では見送ることとし、AWS サポートに改善要望を提出しました。クラウドサービスの利便性向上を目的としたこうした改善要望の起案は、地道ではありますが非常に重要な取り組みであると考えています。 セキュリティ勉強会の準備 最後に、定期的に実施している社内セキュリティ勉強会(セキュリティ&プライバシー勉強会)での登壇資料を作成しました。 SCoE グループでは、プロダクト開発時の「要件定義」「設計」「開発」フェーズにおけるクラウドセキュリティの勘所をまとめた、"クラウドセキュリティガイドライン" を策定し、社内向けに公開しています。 このガイドラインは、KTC が所属するグループ企業のセキュリティポリシーを遵守し、セキュリティリスクを最小限に抑えつつ、効率的な開発を支援するための重要なリソースです。 この "クラウドセキュリティガイドライン" の周知と理解を促進するために、勉強会でのセッションを受け持っています。勉強会では、具体的な事例や実践的なアドバイスを交えながら、ガイドラインの各項目について詳しく説明します。 この日は、IAM(Identity and Access Management)のベストプラクティスについて、持ち時間 20 分に収まるサイズの登壇資料を作成しました。 さいごに KTC のクラウドセキュリティエンジニアのとある一日をご紹介いたしました。業務のごく一部でしたが、業務内容をイメージできましたでしょうか? 我々 SCoE グループでは、一緒に働いてくれる仲間を募集しています。クラウドセキュリティの実務経験がある方も、経験はないけれど興味がある方も大歓迎です。お気軽にお問い合わせください。 詳しくは、 こちらをご確認ください 。
アバター
This article is the entry for day 14 in the KINTO Technologies Advent Calendar 2024 🎅🎄 Introduction Konnichiwa! I am Felix, and I develop iOS applications at KINTO Technologies. Today, I would like to introduce a design tool I recently discovered: Play . While exploring ways to improve development productivity, I was searching for a tool that could translate UI designs into Swift code. That is when I found Play, and it turned out to offer far more features than I had expected. Play allows developers and designers to create, test, and iterate on user interfaces directly on their devices, all while automatically generating SwiftUI code. This tool connects design and development, fostering better collaboration and increasing efficiency. In this post, I will share some of the features I found particularly useful. Designers Can Use Native SwiftUI Components At KINTO, we collaborate closely with designers to ensure our applications align with Apple’s Human Interface Guidelines. However, achieving this can sometimes be challenging, as designers may not always be fully aware of the detailed implementation in iOS. Play allows designers to use native SwiftUI components in their designs. This helps designers better understand what features are readily available in iOS, saving developers from having to reinvent features that are already part of the iOS native library. By leveraging native components, the tool promotes clearer communication between designers and developers and ensures adherence to Apple’s Human Interface Guidelines. Defining Interactions Unlike many design tools that focus solely on UI design, Play enables designers to define actions and animations natively in iOS. This provides a clearer vision of how components are expected to behave. For instance, designers can specify what happens when a button is pressed, how transitions to the next page should look, and how animations should flow. This capability bridges the gap between design and development, making the transition from concept to implementation smoother. Live Preview A particularly time-consuming part of our development process is implementing UI designs and waiting for feedback after publishing a test build. Play helps streamline this process and significantly reduces the waiting time, as designers can check the behavior in the live preview. Once interactions are set up in the design, Play allows designers to test their work directly on a real device. Since the tool builds a demo app using iOS native libraries, designers can experience the exact behavior they would expect in the production version. This feature is incredibly valuable for validating designs and interactions before handing them off to developers. And More... Of course, Play converts UI designs into SwiftUI code, but it goes beyond that to offer additional features that can elevate your workflow. For example, it supports importing existing Figma designs, making migration effortless. Designers can also preview how the UI will look on different screen sizes and devices, ensuring responsive and adaptable designs. I’m sure there are plenty more features I haven’t discovered yet! Our team is highly interested in using this tool, however we need to separate the design for iOS and Android before we try this. If you have the independent designs for iOS and if you are looking to improve team productivity and foster better communication between designers and developers, I would highly recommend giving Play a try.
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の14日目の記事です🎅🎄 学びの道の駅の中西です。今年は学びの道の駅プロジェクトが立ち上がり組織化されました。そんな中、社内Podcastの運営も行っており、今年のアドベントカレンダーではその内容もお届けしたいと思います。 「学びの道の駅」とは? 「学びの道の駅」は、社内で頻繁に開催される勉強会をもっと便利に、そして効果的に活用するために立ち上げられたプロジェクトです。社内の有志が中心となり勉強会の開催を支援し社内の知識共有を促進することを目的としています。 部長会議事メモを読む会 KTC学びの道の駅ポッドキャストでは、KTCの勉強会を開催している方々にインタビューをしています。その名も「突撃!隣の勉強会」。今回は「部長会議事メモを読む会」を開催している大森さんと高木さんにお話を伺います。 インタビュー HOKAさん: では、早速お二人の自己紹介をお願いできますか? 大森さん: はい、コーポレートITの大森です。普段は室町16階センターでパソコンのキッティング作業をしています。アセットプラットフォームチームに所属しており、業務用デバイスやSaaSアカウントライセンスの管理を行っています。新入社員のデバイス準備や回収も担当しています。 高木さん: はい、同じくコーポレートITの高木です。私はテックサービスチームで勤務しており、神保町と室町を行き来しています。サービスデスクとして、社内からの問い合わせに対応したり、問題解決を行っています。具体的には自己サービスマネジメント(GSM)やOPITマネジメントを担当しています。 HOKAさん: ありがとうございます。それでは、部長会議事メモを読む会のきっかけについて教えていただけますか? 大森さん: きっかけは、名古屋にいるきんちゃんが発起人です。コーポレートITは普段の業務で事業の最前線の情報に触れる機会が少ないため、部長会の議事メモを共有し、みんなでインプットし議論することで生産性を向上させようという目的で始めました。 高木さん: 私も同じように感じています。議事メモを読むことで、事業の動きを先読みし、業務に役立てることができます。例えば、申請が来る前に準備を整えることができるので、業務の効率が上がります。 HOKAさん: 実際にこの会を通じてどのような効果がありましたか? 大森さん: 実際の業務に直結することは少ないですが、議事メモを読むことでプロジェクトの背景を理解し、適切な提案ができるようになります。これにより、業務の質が向上します。 高木さん: 同感です。議事メモを読むことで、事業の動きを把握し、突発的な依頼にも対応しやすくなります。議事メモを通じて得た情報は、業務の判断や提案に役立っています。 HOKAさん: これからの展望について教えてください。 高木さん: ファシリテーターのチャレンジの場としても使ってもらいたいです。新しい参加者も増え、より賑やかに、楽しく学べる場にしていきたいです。 大森さん: 同感です。事業理解を深めるために、議事メモを読み、議論することで、参加者全員が業務に役立てられるようにしたいです。情報を蓄積し、後からでもキャッチアップできるようにしていきたいです。 HOKAさん: 最後に、聞いている皆さんに一言お願いします。 大森さん: この会は誰でも参加可能です。興味がある方はぜひ参加してください。一緒に事業理解を深め、業務の質を向上させましょう。 高木さん: これからSlackチャンネルを作成し、告知を行いますので、興味のある方はぜひ参加してください。参加することで、事業理解が深まり、自分の仕事にも役立つと思います。 HOKAさん: 今日はありがとうございました。 今回は部長会議事メモを読む会の詳細と、その運営の背景、今後の展望についてお届けしました。次回の勉強会も楽しみにしてください!
アバター
Introduction Hello. I am Chris, a front-end developer in the Global Development Division at KINTO Technologies. I have written about Storybook and Vue.js , but today, I would like to move away from technology a little, and talk about management. Actually, I became the leader of the front-end team in July of last year, and it's been almost a year since then. It has been my first leadership experience and a many things happened, but I wanted to think about how I can become a better one for next year, look back on what I have done as one over the past year, and set it all down in writing. So, I decided to write this article about it. About the Team First, going back to when I became the team leader last year, the team itself was created when the department reorganized and we wanted a unit that specialized in front-end development. As I mentioned in a previous article, we are a multinational team with some members who are not that fluent in Japanese, so we communicate in basic English within the team. The main job of this team is to do the front-end development for each project that belongs to the Global Development Division, but since there are not many members in relation to the multiple products involved, it is not uncommon for one person to be responsible for several of them. Also, since there are no front-end tasks for products that have entered their maintenance period, we sometimes improve the code by refactoring it, and at the same time rotate team members to other products. Role as the Team Leader Before I became the team leader, I used to focus on front-end development work and develop the design system as a member of this team. However, since becoming the leader, I have mainly focused on the following tasks, and have often left the development work to the other members rather than doing it myself. Selecting technologies and implementation methods The first task is to select the technologies and implementation methods for the team as a whole. There are many frameworks and libraries to choose from for front-end development. In fact, there are so many to choose from that I imagine lots of people probably struggle to decide—and I am one of them, of course. When it comes to frameworks, you have to consider things like whether the team members are used to it, whether it meets the product requirements in terms of functionality, and whether the community support is adequate. However, another important factor is how to strike a balance with developers’ common tendency to want to try out trending frameworks, too. Currently, the front-end team in the Global Development Division is basically uniformly using Vue.js/Nuxt.js, but that does not mean that we will have to continue using this set indefinitely. The team always has an atmosphere of wanting to try new things, so recently, inspired by other front-end teams at in-house tech staff get-togethers, we have been trying out and assessing things like SvelteKit and Astro as well. Then, we decide what to use based on the results of our assessments. Communication and following up between PMs and team members, and reviewing product code The second task is to ensure that product managers (PdMs) / project managers (PjMs) and team members can communicate smoothly. Since the front-end team sometimes does development tasks for multiple products, I get the members to stay in communication with the respective PdM/PjM as they go. Through things like checking specifications, making suggestions, and giving feedback, I help ensure that product development can go smoothly. In addition, since there is often only one team member assigned to each current product, as the leader, I review all the products in order to understand their quality situation and other information about them. Serving as a bridge between senior managers and team members The third task is to serve as a bridge between senior managers and team members. In our company, becoming a manager can sometimes mean having more than 10 extra members under you. For example, in my case, my own direct manager also manages other teams, and is in charge of nearly 20 members in total. Consequently, management of team members is often done via their leader. Currently I mainly do the following: Set roles and missions for each member based on their abilities and aspirations (of course, ultimately coordinate with the manager) Based on the missions we have decided for them, I set up regular 1-on-1 with them, give them feedback, listen to their concerns and the things they want to discuss, and feed the on-site opinions back to the manager as necessary Motivating and mentoring Lastly, of course, managing the team members. As mentioned above, in addition to one-on-one communication, discovering each team member's strengths and making the most of them is also an important role. My team members not only have various nationalities but various skill levels as well. However, one thing they all share is they are highly ambitious. Some of them pay tremendous attention to detail and spot things that are difficult to notice, while others study in their private time then use the skills and knowledge thus gained in their work. It is also necessary to maintain motivation, and I want to help team members achieve their goals as much as possible, but even when that is not possible, communication to motivate them is necessary. Also, if team members need to use a technology that is new to them, I will, for example, give them a lecture on it, or—if it is new to me as well—study it together with them. What I Want to Be Mindful of as a Leader Appropriate communication and following up With working remotely on the rise since the COVID-19 pandemic, our company has also introduced a full-flextime system and a remote-work system. This in turn means more meetings held online. Compared to talking face-to-face, it is harder to pick up information from facial expressions and gestures, and there is a higher risk of communication errors. In particular, when I mainly want to talk to the team members as their leader and for one-on-ones requested by them, as far as possible, I choose times when everyone concerned is in the office. (Of course, we keep the meetings themselves to a minimum.) Also, I won the hackathon held in the department last year and am working with another team to turn my creation into a product. So, I also regret not being able to rapidly follow up with my own members due to doing two jobs, and am now looking for a way to balance things better. Delegating and nurturing well My team works on multiple products, so when assigning members to them, I try to take into account their skill levels and preferences, how difficult the product is, what the issues are, and so on. Then, even after assigning them, I regularly talk to them about the situation and provide them with support. Sometimes when I look at team members’ tasks, I feel a strong desire to do them myself as well. However, I do my best to put my own feelings aside and get the members to do them :->. On the other hand, even after leaving tasks to them, I still have to think about what I can do to help them grow as well. One of my answers is to encourage them to think for themselves as much as possible. For example, in the case of junior-level members, when I want to point out something in a review, first, I ask them questions to get them to think about the “why” part, rather than thinking about everything from scratch. Conclusion Being a leader for a year has brought me a lot of experiences, and I would like to wrap up this article by talking about what might lie in store for the Global Group’s front-end team this fiscal year. Updating our skills in the FE field As I mentioned above, in addition to the team still having some junior-level members, the technologies keep changing day after day. The past few years have seen lots of hot new frameworks in just the front-end field alone. Notable examples are Svelte, Astro, and Qwik, all of which other departments are using. Introducing a new framework or library entails thinking about more than just the technical factors, but studying up on those as well will prove useful in the future, and broaden your horizons to boot. So, I would like to set some vague annual goals for the members and update their skills as a team. Understanding the BE field A common tale from my own experience is that specializing solely in the front-end and having little understanding of the back-end can lead to mismatches in discussion about API integration. So, even though our company separates the front-end work from the back-end in terms of job descriptions, I would like my team to learn as much of the basics as possible in areas their own work does not cover, too. For example, one of the measures I am doing now is to coordinate with the project leaders after assigning members to projects, so that they get to experience handling simple back-end tasks as well, at a level that will not be a nuisance to anyone.
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の13日目の記事です🎅🎄 学びの道の駅の中西です。今年は学びの道の駅プロジェクトが立ち上がり組織化されました。そんな中、社内Podcastの運営も行っており、今年のアドベントカレンダーではその内容もお届けしたいと思います。 「学びの道の駅」とは? 「学びの道の駅」は、社内で頻繁に開催される勉強会をもっと便利に、そして効果的に活用するために立ち上げられたプロジェクトです。社内の有志が中心となり勉強会の開催を支援し社内の知識共有を促進することを目的としています。 Factory自動車勉強会 KTC学びの道の駅ポッドキャストでは、社内の勉強会を開催している方にインタビューを行っています。その名も「突撃!となりの勉強会」。今回はFactoryチームの三浦さんにインタビューしました。 インタビュー HOKAさん: 三浦さん、よろしくお願いします。普段どんなお仕事をされているのか、自己紹介を兼ねてお話しいただけますか? 三浦さん: よろしくお願いします。正式名称を言いますと、プロジェクト開発部プロジェクト推進グループKINTOファクトリーチームのチームリーダーをやっています。ファクトリーチームはKTCだけでなく、KINTOの総合企画部と一緒にプロダクトの開発を行っています。 HOKAさん: 今回のインタビューのきっかけでもある、ファクトリーの中で自動車の勉強をしているということについて教えてください。 三浦さん: まず、みんなに楽しく仕事をしてもらいたいという思いがあります。自分たちが売っているものがどんなものなのかを知ることで、仕事が楽しくなると思います。車に関わる商品を扱っているので、その知識を持っているとより開発が楽しくなります。元々自動車業界でのキャリアがあり、その知識をシェアすることで、もっと良い提案ができるようになると思い、勉強会を始めました。 HOKAさん: その知識をどうやってシェアすることにしたのですか? 三浦さん: オンラインで勉強会を開催しています。最初は1時間だったのですが、現在は30分に縮小して月に一度のペースで行っています。最近では車内のネットワークやナビの進化について話しています。 HOKAさん: 勉強会に参加しているメンバーの反応はどうですか? 三浦さん: アンケートを取ったところ、普段触れることのない情報が新鮮だという意見が多かったです。参加者も減ることなく、興味を持って聞いてくれています。 HOKAさん: どれぐらいの時間やっているのですか? 三浦さん: 最初は1時間でしたが、今は30分で月に一度のペースです。 HOKAさん: これまでの勉強会でどんなテーマを扱いましたか? 三浦さん: 最近では車内のネットワークやナビの進化について話しました。他にも、ラスベガスのCESから見える自動車の進化についても話しました。 HOKAさん: ラスベガスのCESに行かれたのですか? 三浦さん: 実際には行けなかったのですが、ネットで公開されている展示内容をもとに自分なりの考察を加えてシェアしました。 HOKAさん: 今後の勉強会の予定はありますか? 三浦さん: 次回は、車の取り付け工程について話そうと思っています。販売店向けのマニュアルを使って、車にどのように部品が取り付けられているのかを共有する予定です。 HOKAさん: 勉強会に参加したい場合はどうすればいいですか? 三浦さん: 基本的にはオンラインで行っているので、関心がある方は自由に参加できます。今後、勉強会の情報を可視化する仕組みも作ろうとしています。 HOKAさん: 勉強会を始めるきっかけは何でしたか? 三浦さん: チームビルディングの一環として始めました。もともとプロジェクトチームだったものがチームになったことで、チーム内のメンバーに自動車についてもっと知ってもらおうと思ったのがきっかけです。 HOKAさん: 最後に、社員の皆さんに一言お願いします。 三浦さん: 我々は自動車に関わる事業会社で働いているので、車について知識を深めることで仕事がより楽しくなると思います。興味がある方は、ぜひ勉強会に参加してください。 HOKAさん: 三浦さん、ありがとうございました。これからも勉強会を通じて、社員の皆さんに自動車の魅力を伝えていってください。 今回はFactoryの詳細と、その運営の背景、今後の展望についてお届けしました。次回の勉強会も楽しみにしてください!
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の13日目の記事です🎅🎄 Impact Effort Matrix(インパクト・エフォートマトリックス)を使って社内交流を実践してみた こんにちは、KINTO テクノロジーズの技術広報グループに所属しているMayaと木下です。 はじめに 私たちは過去に神保町で社内交流会を開催し、オフィス内の交流不足を解消し、チーム間のつながりを強化することを目指しました。 実施したときの記事はこちら↓ https://blog.kinto-technologies.com/posts/2023-09-19-JimbochoISM/ このイベントでは、企画段階にImpact/Effort Matrix(インパクト・エフォートマトリックス)を活用して、催しの内容を決定しました。 これにより、イベントを盛り上げるためのアイデアを整理し、タスクの優先順位を明確にすることができました。 その結果、複数回のイベントをスムーズに運営することができました。 この記事では、その時の経験を基に、Impact/Effort Matrixを用いたイベントの企画・実践、振り返りまでのアプローチを紹介します。 本記事が、読者の皆さんのプロジェクトやイベント計画に役立てば幸いです。 ぜひ、最後までお楽しみください。 Impact/Effort Matrixについて Impact/Effort Matrix(インパクト・エフォートマトリックス)は、プロジェクトやタスクの優先順位を効率的に決定するためのシンプルかつ効果的なツールです。 このマトリックスは、タスクやアイデアを 「成果(Impact)」 と 「労力(Effort)」 の2軸で分類することで、どの項目にリソースを集中させるべきかを視覚的に判断できます。 基本構造 Impact/Effort Matrix は4つの象限で構成されます: Quick Wins(すぐに実行すべきタスク) 高い成果をもたらし、必要な労力が少ないタスク 優先的に取り組むべきです Major Projects(戦略的に取り組むべきタスク) 高い成果が期待できるが、労力も多く必要なタスク リソース計画が重要になります Fill-ins(余裕があるときに実施するタスク) 労力が少なく成果も小さいタスク 優先順位は低めです Parking Lot(避けるべきタスク) 成果が低く、労力が多いタスク 基本的に取り組むべきではありません さらに詳しくは Miroのテンプレート説明ページ をご参照ください。 なぜ導入したのか 第1回目の神保町共有会開催後、それなりに良い感想をいただくことができました。 しかし、参加者全員が一体感を持てているわけではなかったため、もっと「ワイワイ感」を出すにはどうすればいいのか?というブレインストーミングをしたかったのです。 運営メンバーで議論し合いながら、目指す目的を明確にし、やるべきタスクとやらないタスクを選定しました。 そして、優先順位をメンバーで共有するための適切な手法を探している中、メンバーが過去に利用経験のあるImpact/Effort Matrixを用いることにしました。 活用して感じたメリット 優先順位付けが明確になる: 4象限に相対的に全ての課題を配置するので、議論を進め、付箋を並べるにつれて、優先するべきタスクが視覚的に浮き上がってくる 容易に共有でき、全員の理解が一致しやすい: 上記の延長線ではありますが、何を優先すべきかを議論するきっかけにもなるため、認識をチームで合わせる時間になります 全員のコンセンサスを得ながら進めるため、コミットメントも高く、全員が自分ごとと捉えて進めやすい チームのリソースを最適化できる: 次のアクションへも全員が納得する落とし込みにもつながるため、出戻りの発生を防げます 苦労したところ・工夫したところ よくある話ではありますが、一番チームで苦労したところは意思決定の部分でした。 2回実施したImpact/Effort Matrixの初回では、意見や共通認識の課題が多く出たものの、その情報をどう整理し、次のイベントの改善に繋げるかのコンセンサスに至るまでたくさん議論を重ねる必要がありました。 整理した内容の粒度がバラバラで、やりたいことのスコープも広かったため、思うように成果を出せませんでした。 2回目では、上記の経験を踏まえて課題の粒度を揃え、目的を明確にし、スコープを絞ることで、より具体的な結果を得ることができました。 メンバーの理解が深まり、プロセスをスムーズに進めることができました。 イベントの方向性をより具体化することができて、二回目も無事に成功し、神保町共有会の三回目に向けたアクションプランを策定し、Jiraボードでタスクを見える化しました。 この時、新しいメンバーが数名加わり、初めてIEMを体験する人たちでした。 未経験者を多く含む状況ながらも、これまでの経験からスムーズに進行することができ、三回目の神保町共有会を盛り上げることができました。 Impact/Effort matrixやってみての感想 Impact/Effort Matrixの手法を理解しても、実際に自分たちの状況に当てはめて実践しようとすると、最初は本当にこれで良いのかと迷うことがあり、チーム全体に不安が広がることもありました。 しかし、違和感を覚える点や改善すべき点について、皆が意見を出し合い、それを尊重し合うことで、私たちは様々なアイデアを整理し、どのタスクを優先すべきかを明確にすることができました。 このImpact/Effort Matrixの手法が上手く機能するまでの過程では、意見の対立もありましたが、それぞれのアイデアを客観的に評価することで、全員が納得できる形で進めることができました。 イベントに向けて準備が進むにつれ、チームとしての一体感が高まり、最終的には社内交流会の参加者から高い評価を得ることができました。 終わりに 今回の記事では、神保町での社内交流会を例に、Impact/Effort Matrixを活用したイベントの企画・実践、振り返りのアプローチをご紹介しました。 初めは手探りで不安もあり、うまくできているのか自信が持てない部分もありましたが、何度か実践を重ねる中で次第に慣れ、運営メンバーが途中で増えたにもかかわらず、スムーズに運営できるようになりました。 この手法が、皆さんのプロジェクトやイベントの成功に役立つことを願っています。 皆さんのプロジェクトやイベントの参考になれば幸いです。 KINTO テクノロジーズでは、一緒に働く仲間を広く募集しています。 ご興味を持たれた方は、ぜひお気軽にご連絡ください。お待ちしております! https://hrmos.co/pages/kinto-technologies/jobs
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の13日目の記事です🎅🎄 はじめまして。 KINTOテクノロジーズでUnlimited(Android)を開発しているfabtと申します。 最近何かと話題のスクリーンショットテストを導入してみたので、導入の流れを追いながら、つまづいた点とその解決方法について紹介します。 スクリーンショットテストの導入を考えている方の参考になれば幸いです。 スクリーンショットテストとは 開発中のソースコードを基に画面のスクリーンショットを撮影し、過去のソースコードを基に同様の手順で撮影されたスクリーンショットと比較し、変更点を確認・検証するテスト手法です。(ざっくりですが・・・) 人間の目では認識しづらい1dpの差も検出してくれるため、UIに意図しない変更が含まれていないかを容易に判断できます。 DroidKaigi 2024などのカンファレンスでも多くのセッションがあり、最近話題になっていると感じます。 スクリーンショットテストライブラリの選定 カンファレンスなどでの発表も多く、弊社の他のAndroid向けアプリでも導入実績のある roborazzi を採用することにしました。 なにはともあれ実行してみる 公式 のセットアップ手順やインターネット上の情報をもとに導入し、ローカル環境で実行してみます。 ひとまず特殊なことはしていないので、さっくり進めていきます。 まずはスクリーンショットテスト実行に必要なライブラリを導入します。 バージョンカタログ(libs.versions.toml) [versions] robolectric = "4.13" roborazzi = "1.29.0" [libraries] androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" } roborazzi-compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" } roborazzi-junit-rule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" } [plugins] roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } rootのbuild.gradle.ktsファイル plugins { alias(libs.plugins.roborazzi) version libs.versions.roborazzi apply false } moduleのbuild.gradle.ktsファイル plugins { alias(libs.plugins.roborazzi) } android { testOptions { unitTests { isIncludeAndroidResources = true all { it.systemProperties["robolectric.pixelCopyRenderMode"] = "hardware" } } } } dependencies { // robolectric testImplementation(libs.androidx.compose.ui.test.junit4) debugImplementation(libs.androidx.compose.ui.test.manifest) testImplementation(libs.robolectric) // roborazzi testImplementation(libs.roborazzi) testImplementation(libs.roborazzi.compose) testImplementation(libs.roborazzi.junit.rule) } これで必要なライブラリを導入できたはずです。 下記のようなテスト用クラスを作成してとりあえずローカルで実行してみましょう! import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot import androidx.test.ext.junit.runners.AndroidJUnit4 import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH import com.github.takahirom.roborazzi.captureRoboImage import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.GraphicsMode @RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class ScreenShotTestSample { @get:Rule val composeTestRule = createComposeRule() @Test fun sample() { composeTestRule.apply { setContent { MaterialTheme { Surface { Text(text = "screen shot test sample") } } } onRoot().captureRoboImage( filePath = "$DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH/sample.png" ) } } } 下記のスクリーンショット保存コマンドをAndroid Studioのターミナルから実行すれば Text(text = ・・・) のスクリーンショットがpngとして出力されるはずです。 ./gradlew recordRoborazziDebug ![](/assets/blog/authors/f.tsuji/2024-12-13/folder_empty_roborazzi.png =750x) なんと、何も出力されませんでした。 予想外の結果です。 テストがスキップされる コマンド実行は成功しているのに、なぜかスクリーンショットの結果が出力されませんでした。 調査しつつローカルでユニットテストを実行してみると、以下のような表示がされていることに気がつきました。 ![](/assets/blog/authors/f.tsuji/2024-12-13/Test_events_were_not_received.png =750x) なぜかテストイベントを受信できておらずスキップされているようです。 たしかに、それであればコマンド成功かつ結果出力なしも納得できます。 さらに調査を進めていった結果、JUnitバージョンの競合が原因のようでした。 弊アプリでの通常のユニットテストはkotestを使用しているのですが、そちらのテストランナーはJUnit5、 roborazzi(というよりもrobolectric)のテストランナーがJUnit4であることが問題でした。 なんと roborazziのissueにも似たような問題がありました。 解決策は、上記issueにもある通り junit-vintage-engine という ライブラリ を使用することでした。 上記ライブラリはざっくりいうとJUnit4とJUnit5を共存させることができ、移行にも使われることがあるとのことです。 それでは junit-vintage-engine を導入し、再度コマンドを実行してみることにします。 まずは依存関係を追加(前段で導入した部分は割愛) バージョンカタログ(libs.versions.toml) [versions] junit-vintage-engine = "5.11.2" [libraries] junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit-vintage-engine" } moduleのbuild.gradle.ktsファイル dependencies { testImplementation(libs.junit.vintage.engine) } 再度スクリーンショット保存コマンドを実行します。今度こそ Text(text = ・・・) のスクリーンショットがpngとして出力されるはずです。 ![](/assets/blog/authors/f.tsuji/2024-12-13/build_failure.png =750x) 実行結果 :::message alert junit-vintage-engine を導入し、JUnitバージョンを共存させたことでテストは実行できましたが、結果は失敗でした。 ::: なにかしらの初期化に失敗 なんと実行に失敗してしまったようです。 ライブラリを追加する前は実行すらできていなかったので、ポジティブに一歩前進と捉えて愚直に解決を目指しましょう。トライアンドエラーです。 問題解決の最初の一手ということでログを見てみると、以下が出力されていました。 ![](/assets/blog/authors/f.tsuji/2024-12-13/build_failure_log.png =750x) なにかしらの初期化に失敗しているようです。 出力されたログを確認していくと、どうも弊アプリのApplicationクラスを初期化しようとしているみたいです。 Robolectricの設定 を詳しく調査すると、以下の記載を見つけました。 Robolectric will attempt to create an instance of your Application class as specified in the AndroidManifest. Robolectricは、 AndroidManifest.xml で指定されたApplicationクラスのインスタンスを作成しようとするようです。 ログの内容と一致しました! 初期化に失敗しないよう、公式を参考に @Config アノテーションを使用しシンプルなApplicationクラスを使用するよう伝えてみることにします。 import android.app.Application import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) @Config(application = Application::class) // 追加 @GraphicsMode(GraphicsMode.Mode.NATIVE) class ScreenShotTestSample { 期待を込めて、スクリーンショット保存コマンド(割愛)をAndroid Studioのターミナルから実行します。 さすがに今度こそText(text = ・・・)のスクリーンショットがpngとして出力されるはずです。 ![](/assets/blog/authors/f.tsuji/2024-12-13/build_success.png =250x) ![](/assets/blog/authors/f.tsuji/2024-12-13/folder_roborazzi.png =750x) ![](/assets/blog/authors/f.tsuji/2024-12-13/result_sample.png =250x) スクリーンショットが出力されました! :::message Robolectric の設定を調査し、 @Config アノテーションを用いて使用するApplicationクラスを明示することでスクリーンショットテストが成功しました。 ::: 比較(スクリーンショットテスト)も試してみるべく、テスト用クラスを少しだけ変更しコマンドを実行してみます。 // Text(text = "screen shot test sample") Text(text = "compare sample") // textを変更 ./gradlew compareRoborazziDebug ![](/assets/blog/authors/f.tsuji/2024-12-13/folder_compare_roborazzi.png =750x) 比較結果が出力されました! ![](/assets/blog/authors/f.tsuji/2024-12-13/result_sample_actual.png =250x) 比較画像 ![](/assets/blog/authors/f.tsuji/2024-12-13/result_sample_compare.png =750x) 比較結果 成功したのでここまでのまとめ 簡易的なテストクラスを作成し、無事にスクリーンショットテストを実行できました。 導入しているユニットテストやプロパティの指定など、プロジェクトによって細かい部分の調整が大変な印象ですが、 変更を目で確認できるのは明瞭かつ高速なため、可能であれば導入を検討するとアプリの品質を担保しやすくなるかと思います。 余談 弊アプリではこのタイミングでCIの実装も行っています。 GitHub Actions Workflowを使用し、スクリーンショットの保存・比較・コメントをPRにするところまで実現しています。 特別テクニカルなことをしているわけではないため割愛しますが、 公式 にあるように companion branch approach を用いて結果を保持する手法を選択しています。 (ymlファイルの統合などは行いましたが、導入する際の一般的なレベルの最適化程度です。) :::message ./gradlew recordRoborazziDebug を実行した際、同一モジュールに通常のUnitTestがあるとそちらも実行されてしまいます。 弊アプリでは独自のプロパティを定義し、UnitTestを実行するためのGradleタスクとRoborazziを用いたスクリーンショットテスト用のタスクとに分離し管理しています。 参考issue https://github.com/android/nowinandroid/issues/911 https://github.com/takahirom/roborazzi/issues/36 ::: 閑話休題。 Preview関数もテスト対象にする Composable関数を実装する際にPreview用関数も同時に作成することが多いと思います。 この記事の前半部分のように手動でテストも実装するとなると、 Composable実装 Preview実装 テスト実装 となかなか大変です・・・。 せっかくならばPreview関数をそのままスクリーンショットテストの対象にしてしまおう!というのが本項の内容です。 こちらも特に話題の中心になっている技術ですね。 なにはともあれ実行してみる 手順は概ね以下です。 Preview関数を収集する 収集した関数をスクリーンショットテストする シンプルですね。 Preview関数の収集にはさまざまな方法があると思いますが、今回は ComposablePreviewScanner を使用していこうと思います。 導入手順がまとめられていること(調査がしやすいこと)、将来的に公式でも連携・サポートされそうなこと( 執筆時点ではExperimental )が主な理由です。 では、必要なライブラリを導入していきましょう。 バージョンカタログ(libs.versions.toml) [versions] composable-preview-scanner = "0.3.2" [libraries] composable-preview-scanner = { module = "io.github.sergio-sastre.ComposablePreviewScanner:android", version.ref = "composable-preview-scanner" } moduleのbuild.gradle.ktsファイル dependencies { // screenshot testing(Composable Preview) testImplementation(libs.composable.preview.scanner) } 続いて、composable-preview-scannerのREADMEや先人たちの情報を参考にテスト用クラスを作成してみましょう。 import android.app.Application import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot import com.github.takahirom.roborazzi.DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH import com.github.takahirom.roborazzi.captureRoboImage import com.kinto.unlimited.ui.compose.preview.annotation.DialogPreview import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.ParameterizedRobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview @RunWith(ParameterizedRobolectricTestRunner::class) class ComposePreviewTest( private val preview: ComposablePreview<AndroidPreviewInfo> ) { @get:Rule val composeTestRule = createComposeRule() @Config(application = Application::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Test fun snapshot() { val fileName = AndroidPreviewScreenshotIdBuilder(preview).ignoreClassName().build() val filePath = "$DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH/$fileName.png" // Preview関数名.png composeTestRule.apply { setContent { preview() } onRoot().captureRoboImage(filePath = filePath) } } companion object { private val cachedPreviews: List<ComposablePreview<AndroidPreviewInfo>> by lazy { AndroidComposablePreviewScanner() .scanPackageTrees( include = listOf("・・・"), // Preview関数を探すパッケージを設定する exclude = listOf() ) .includePrivatePreviews() // PrivateなPreview関数も含める .getPreviews() } @JvmStatic @ParameterizedRobolectricTestRunner.Parameters fun values(): List<ComposablePreview<AndroidPreviewInfo>> = cachedPreviews } } では、スクリーンショット保存コマンドを実行します。 ・・・したのですが。 やたらと時間がかかり1時間経っても終わる気配がありません。Preview関数の数も200弱なのでそこまでかかるかな?といった感じです。さすがにすんなりとはいかないですね。 :::message alert Preview関数をスクリーンショットテストの対象に含めたところ、テスト実行にとても長い時間がかかるようになってしまいました。 ::: テスト実行に時間がかかる 色々調べていくうちに似たような症例を見つけました。 https://github.com/takahirom/roborazzi/issues/388 1つのテストを完了するのに約5分かかり CircularProgressIndicator を削除したところ高速で実行されたとのこと。 Issueのやりとりを深掘りしていくと、どうやら無限アニメーションを含むComposableをテストしようとすると時間がかかっている模様。 解決策としては mainClock.autoAdvance = false を設定し、Compose UIとの自動同期を停止した上で手動で時間を進める方法が紹介されていました。 手動で時間を操作することで任意のタイミングでスクリーンショットを撮影できるため無限アニメーションによる影響を受けない、ということのようです。 弊アプリでもまさに CircularProgressIndicator を使用しているため、これは試すしかない・・・!と早速実装してみます。 mainClock. ・・・ の箇所をテストクラスに追加しました。 時間を止め、 1_000ミリ秒(1秒)進め、 スクリーンショットを撮影し、 時間停止を解除する。 という流れですね。 composeTestRule.apply { mainClock.autoAdvance = false // 1 setContent { preview() } mainClock.advanceTimeBy(1_000) // 2 onRoot().captureRoboImage(filePath = filePath) // 3 mainClock.autoAdvance = true // 4 } (別件ですが、画像を非同期で読み込みための coil というライブラリがありますが、そちらも 正しくテストできない可能性があるとのこと。 ただ解決方法は同様そうなのでまとめて対応できそうです。) さあ、実行です!!! ![](/assets/blog/authors/f.tsuji/2024-12-13/build_failure_time.png =500x) 高速で実行できるようになったものの、失敗してしまいました。 :::message alert 無限アニメーションがボトルネックとなっていましたが、テスト時に手動で時間を操作することで解消。高速でテストを実行できるようになりましたが、結果は失敗でした。 ::: ダイアログを含んでいると失敗する 実行速度は上がったものの、失敗しては意味がありません。こういう時に頼れるのはやはりログですね。 ComposePreviewTest > [17] > snapshot[17] FAILED java.lang.AssertionError: fail to captureRoboImage Reason: Expected exactly '1' node but found '2' nodes that satisfy: (isRoot) Nodes found: 1) Node #534 at (l=0.0, t=0.0, r=0.0, b=0.0)px 2) Node #535 at (l=0.0, t=0.0, r=320.0, b=253.0)px Has 1 child at androidx.compose.ui.test.SemanticsNodeInteraction.fetchOneOrThrow(SemanticsNodeInteraction.kt:178) at androidx.compose.ui.test.SemanticsNodeInteraction.fetchOneOrThrow$default(SemanticsNodeInteraction.kt:150) at androidx.compose.ui.test.SemanticsNodeInteraction.fetchSemanticsNode(SemanticsNodeInteraction.kt:84) at com.github.takahirom.roborazzi.RoborazziKt.captureRoboImage(Roborazzi.kt:278) at com.github.takahirom.roborazzi.RoborazziKt.captureRoboImage(Roborazzi.kt:268) at com.github.takahirom.roborazzi.RoborazziKt.captureRoboImage$default(Roborazzi.kt:263) at com.kinto.unlimited.ui.compose.ComposePreviewTest.snapshot(ComposePreviewTest.kt:49) 上記のようなログが複数出力されていました。2つのnodeがあるとのこと。 ログの情報で検索すると 気になるIssue を見つけました。 compose-multiplatform についてなので原因は違うかもしれませんが、たしかに失敗しているのは Dialog()Composable を使用している箇所のようでした。 諦めるしかないのか・・・と悩んでいたところ、Experimentalとしてダイアログを含むイメージをキャプチャする 関数が追加されている とのことです! Experimentalのため全てのテストに使用するのは憚られますが、テスト対象がダイアログかどうかを判定できれば成功するかもしれません・・・。 ということで DialogPreview() というカスタムアノテーションを作成し、ダイアログを含むPreviewに付与、テストクラスで情報を取得し判定することにしました。 annotation class DialogPreview() @OptIn(ExperimentalRoborazziApi::class) // 追加 @Config(application = Application::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Test fun snapshot() { val isDialog = preview.getAnnotation<DialogPreview>() != null // 追加 composeTestRule.apply { mainClock.autoAdvance = false setContent { preview() } mainClock.advanceTimeBy(1_000) if (isDialog) { // 追加 captureScreenRoboImage(filePath = filePath) } else { onRoot().captureRoboImage(filePath = filePath) } mainClock.autoAdvance = true } } companion object { private val cachedPreviews: List<ComposablePreview<AndroidPreviewInfo>> by lazy { AndroidComposablePreviewScanner() .scanPackageTrees( include = listOf("・・・"), exclude = listOf() ) .includePrivatePreviews() .includeAnnotationInfoForAllOf(DialogPreview::class.java) // 追加 .getPreviews() } } previewに DialogPreviewアノテーション が付与されているか(nullではないか)を判定し、ダイアログの場合はcaptureScreenRoboImage()を使用するようにしました。 実行してみます。 ![](/assets/blog/authors/f.tsuji/2024-12-13/build_success.png =250x) ![](/assets/blog/authors/f.tsuji/2024-12-13/folder_roborazzi_preview.png =750x) Preview関数の性格上、画像やファイル名をマスクしています。 Preview関数を読み込み、それらのスクリーンショットを保存できました! :::message ダイアログを含むComposableは、Nodeを複数持ってしまうため正しくRootを判断できずにテスト実行で失敗していました。 Experimentalではありますが、ダイアログをキャプチャできる関数を使用することで正常にテストを実行できました。 ::: スクリーンショットの比較は前段で確認済なので、そのままで問題なさそうです。 長々とお付き合いいただきありがとうございました。 最後のまとめ この記事では、スクリーンショットテストの導入の流れを追いながら、つまづいた点とその解決方法について紹介しました。 当初はライブラリを追加して実行するだけだと思っていましたが、なかなかうまくいかず、試行錯誤を通じてなんとかスクリーンショットテストを実行できるようになりました。 つまづいた点の解決方法はライブラリの追加やアノテーションの付与など、対応としては単純でしたが、そこに辿り着くまでの情報が少なく、意外と大変でした。 この記事が少しでも皆さんのお役に立てば幸いです。
アバター
この記事は KINTO テクノロジーズアドベントカレンダー 2024 の 13 日目の記事です 🎅🎄 はじめに こんにちは。KINTO ONE開発部の新車サブスク開発グループでフロントエンド開発を担当しているITOYUです。 エンジニアの皆さん、GitHub を使っていますか?我々 KINTO テクノロジーズでも GitHub を利用しています。 チーム開発をする上で Pull Request 機能を使ってコードレビューとマージを行っています。 マージの実行時にオプション指定をすることが出来るのですが、オプション指定をすることが出来るのをご存知でしょうか? 各オプションの違いの説明と、私が過去に躓いた罠について説明します。 何を説明するのか GitHub 上での Pull Request のマージオプション Create a merge commit Squash and merge Rebase and merge 罠 Rebase and merge と git rebase は一緒じゃない 自分が前に実行したオプションが次回のデフォルトになる 前提 develop ブランチと feature ブランチが存在している develop ブランチから feature ブランチを切って作業をして、develop ブランチに対して Pull Request を作成する develop ブランチと feature ブランチのコミット履歴は以下のようになっている develop ブランチの commit 履歴 feature ブランチの commit 履歴 GitHub 上での Pull Request のマージオプション Create a merge commit Create a merge commit は、feature ブランチのコミットを hash 値を保持したまま develop ブランチにマージした後、新たにマージコミットを作成します。 実際にマージを行うと以下のようなコミット履歴になります。 マージ後の develop ブランチの commit 履歴 hash 値が保持されたまま develop ブランチにマージされていることが確認できます。そして新たにマージコミットが作成されていることが確認できます。 特徴 マージ元のコミット履歴の hash 値が保持される マージコミットが作成されることでマージによる形跡が残る 使用例 複数のコミットをそのまま保持したい場合 マージの履歴を明確に残したい場合 Squash and merge Squash and merge は、feature ブランチのコミットを 1 つのコミットにまとめて develop ブランチにマージします。 実際にマージを行うと以下のようなコミット履歴になります。 マージ後の develop ブランチの commit 履歴 feature ブランチでは複数の commit がありましたが、develop ブランチには 1 つの commit しか存在していません。 特徴 マージ元のコミット履歴が1つにまとめられる 使用例 コミット履歴をシンプルに保ちたい場合 小さな変更をまとめて1つのコミットにしたい場合 Rebase and merge Rebase and merge は、feature ブランチのコミットを develop ブランチの最新のコミットの直後にコピーし、develop ブランチにマージします。 その際コミットはまとめられず、feature ブランチのコミット履歴がそのまま develop ブランチにマージされます。 マージ後の develop ブランチの commit 履歴 この際マージコミットが作成されていないことが確認できます。 特徴 マージ元のコミット履歴がそのまま develop ブランチにマージされる マージコミットが作成されないので、コミット履歴が綺麗になる マージ元の hash 値とは異なり、新たなコミットとして作成される 使用例 コミット履歴をそのまま保持しつつ、マージコミットを作成したくない場合 リベースを行ってコミット履歴を整理したい場合 罠 上記では各オプションの説明をしました。ここからは私が過去に躓いた罠について説明します。 Rebase and merge と git rebase は一緒じゃない 中規模プロジェクト用の開発ブランチを用意して共同で開発をしていました。 そこでブランチ元のdevelopブランチが更新されたのでプロジェクト用のブランチを git rebase を利用してコミット履歴を整理しようと考えました。 ただそうなると force push が必要になるので、共同で作業しているブランチには force push を避けたいと考えました。 そこでGitHub PullRequest機能のオプションの Rebase and merge を使えば代用可能なのではないかと考えました。 そうすることでコミット履歴も綺麗に保たれ、ローカルでの作業もなくなり安全だと考えました。 さて、develop ブランチに feature ブランチから出した Pull Request を Rebase and merge オプションを使ってマージを行った後、差分が無いかチェックして見ました。 めちゃくちゃ差分がありました。 一見 develop ブランチと feature ブランチのコミット履歴は同じに見えますが、hash 値が異なっていました。 Rebase and merge の特徴の1つに「マージ元の hash 値とは異なり、新たなコミットとして作成される」というものがあるためです。 Rebase and merge を実行した時と git rebase を実行した時には挙動が異なるので、同一の挙動を期待してはいけないと学びました。 自分が前に実行したオプションが次回のデフォルトになる これは罠というか、不注意によるミスです。 前提として、私のチームではいつも Squash and merge を利用して作業ブランチのコミット履歴を綺麗に保っています。 先ほど Rebase and merge による実験をして失敗に終わった後、通常の作業に戻りました。 そして私の出した Pull Request が approve されたのでいつも通りマージを行いました。 そこで気付く違和感。何かがおかしい。 なぜか Rebase and merge が実行されている... Pull Request のマージオプションは、自分が前に実行したオプションが次回のデフォルトになるようです。ちょっと考えれば当たり前のことですね。 今まで意識していなかったので気付かなかったのですが、オプションをいじる際は次の Pull Request にも影響があるので注意が必要だと学びました。 まとめ GitHubのPull Requestマージ時のオプション指定の特徴を踏まえた上で、目的に合わせて適切なオプションを選択することが重要です。 私は以下のような使い分けをしています。 Create a merge commit : マージ元のコミットハッシュを保持し、マージの履歴を明確に残したい場合に使用します。これにより、どのブランチがどのタイミングでマージされたかが一目でわかります。 Squash and merge : 作業コミットを1つにまとめて、コミット履歴をシンプルに保ちたい場合に使用します。これにより、細かいコミットが1つにまとめられ、履歴が見やすくなります。 Rebase and merge : マージコミットを作成せずに、コミット履歴を直線的に保ちたい場合に使用します。これにより、変更の流れが追いやすくなり、履歴が綺麗になります。 また、Pull Requestをマージする際には、今設定されているオプションが何なのかを必ず確認してからマージを行うことで予期せぬ事故を防ぐようにしましょう。
アバター
こんにちは。 DBRE チーム所属の @p2sk と @hoshino です。 DBRE(Database Reliability Engineering)チームでは、横断組織としてデータベース(DB)に関する課題解決やプラットフォーム開発に取り組んでいます。 本記事では、AWS の生成 AI サービス Amazon Bedrock を活用し、Serverless アーキテクチャで構築した DB テーブル設計の自動レビュー機能を紹介します。この機能は GitHub Actions と連携し、プルリクエスト(PR)がオープンされると AI が自動でレビューし、修正案をコメントとして提案します。また、生成 AI アプリケーションの評価をどのように設計・実装したかも解説します。LLMOps のライフサイクルにおける 3 つのフェーズ(モデル選定、開発、運用)ごとに採用した評価手法を説明し、特に運用フェーズでは「LLM-as-a-Judge」を活用した生成 AI による自動評価について紹介します。 本記事の目的 本記事では生成 AI アプリケーションの評価を、抽象的な概念から具体的な実装例までわかりやすく情報提供することを目指します。本記事を読んでいただくことで、私たち DBRE チームのように機械学習の専門知識がないエンジニアでも、生成 AI の開発ライフサイクルの理解度が上がることを目指しています。また、生成 AI をサービスで活用する中で直面した課題とその解決策についても具体例を交えて紹介します。加えて、先日開催された AWS AI Day でのセッション「 コンテンツ審査を題材とした生成AI機能実装のベストプラクティス 」で紹介されている「LLM の実導⼊における考慮点と打ち⼿」に対する実装例の 1 つとしてもお読みいただけるかと思います。 少しでも皆さまの参考になれば幸いです。 目次 この記事の構成は以下のとおりです。長文のため「どんな仕組みか」にだけ興味がある方は「完成した仕組み」まで、生成 AI アプリケーション開発に関心がある方はそれ以降も読んでいただければと思います。 背景 設計 完成した仕組み(デモ動画あり) 実装時の工夫点 生成 AI アプリケーションの評価 得られた学びと今後の展望 まとめ 背景 DB のテーブル設計の重要性 一般的に DB のテーブルは、一度作成すると修正が難しいという特性があります。サービス成長に伴いデータ量や参照頻度は増加する傾向にあるため「設計時点で⚪︎⚪︎にしておけばよかった・・・」と後から後悔するような技術的負債を抱え続けることは、できる限り避けたいものです。したがって、統一された基準に基づく「良い設計」でテーブルが作り続けられる仕組みを整えることが重要です。「 AWSでデータベースを始めるのに 必要な 10 のこと 」においても、クラウドで DB 管理が自動化されてもテーブル設計は依然として価値の高いタスクとされています。 また、生成 AI の普及によりデータ基盤の重要性はさらに高まっています。統一基準で設計されたテーブルは分析がしやすく、分かりやすい命名や適切なコメントは生成 AI に良質なコンテキストを提供できる、というメリットもあります。 こうした背景から、DB テーブル設計の質が組織に与える影響は以前よりも大きくなっています。質を担保する手段として、例えば社内ガイドラインの作成や、それに基づくレビューの実施が考えられます。 レビューに関する弊社の現状 弊社では、テーブル設計レビューは各プロダクトの担当者が行っています。DBRE チームから「設計ガイドライン」を提供していますが、現状のところ拘束力はありません。DBRE が全プロダクトのテーブル設計を横断的にレビューする案も検討しましたが、プロダクトが数十個に及ぶため、DBRE がゲートキーパー的に振る舞うと開発のボトルネックになる懸念があり、断念しました。 以上の背景から、ガードレール的に振る舞う自動レビューの仕組みを DBRE で開発してプロダクトへ提供する、という手段を採用しました。 設計 抽象的なアーキテクチャ図と機能要件 以下は、自動テーブル設計レビュー機能の抽象的なアーキテクチャ図です。 自動レビューを継続的に実行するには、開発フローへの統合が重要です。そのため、PR をトリガとして AWS 上でアプリケーションを自動実行し、PR 内にテーブル定義(DDL)の修正案をコメントでフィードバックする仕組みを採用しました。アプリケーションの要件は以下の通りです。 弊社独自のレビュー観点を設定できること 人間のレビューを補完する目的で、100%でなくとも可能な限り高精度であること レビュー機能の実現方針 テーブル設計レビューの自動化には「① 構文解析によるレビュー」と「② 生成 AI によるレビュー」の 2 種類の方針が考えられます。それぞれの特徴を以下にまとめます。 構文解析で対応可能なレビュー観点は①、それ以外は②を適用するのが理想です。たとえば「オブジェクト名は Lower Snake Case で定義する」という命名規則の確認は①で対応できます。一方で「格納されているデータを推測できるオブジェクト名をつける」などの主観的な観点は②が適しています。 このように、レビュー観点に応じて両者を使い分けるのが理想ですが、今回は以下の理由から「②生成 AI によるレビュー」のみで実装する方針としました。 ①は実現可能性が見えているが、②はやってみないと分からず、先に挑戦する価値があると判断 ①で対応可能な項目も②で実装することで、両者の精度や実装コストの比較に関する知見を得たい レビュー対象のガイドライン 提供までの時間を短縮するため、レビュー項目を以下の 6 つに絞りました。 インデックスが DBRE 指定の命名規則に準拠 オブジェクト名は Lower snake case で定義 オブジェクト名は英数字とアンダースコアのみで構成 オブジェクト名にローマ字を使わない 格納されているデータを推測できるオブジェクト名をつける 真偽値を格納するカラムは「flag」を使わずに命名 上の 3 つは構文解析で対応可能ですが、下の 3 つは修正案の提示も考慮すると生成 AI の方が適切だと考えられます。 なぜ専用の仕組みを作るのか 「生成 AI によるレビューの仕組み」は既に複数存在しますが、今回の要件を満たせないと判断し、専用の仕組みを作ることにしました。例えば、 PR-Agent や CodeRabbit は有名な生成 AI レビューサービスで、弊社でも PR-Agent を導入し、 コードやテックブログのレビューに活用 しています。また、 GitHub Copilot の自動レビュー機能 は現在 Public Preview として提供中で、今後 GA される可能性があります。この機能ではコードを Push する前に Visual Studio Code 上でレビューを受けることも可能で、「生成 AI によるレビューの仕組み」は今後さらに開発フローに自然に統合されていくと想定されます。さらに、GitHub の管理画面で独自のコーディング規約を定義し、それをもとに Copilot にレビューさせることも可能です。 それでも自前で仕組みを構築する理由は以下の通りです。 多数のガイドラインを生成 AI で高精度にチェックするのは難しく、外部サービスでの対応は現状困難と判断 フィードバックの方法を柔軟に調整したい 例:意味が曖昧な「data1」のようなカラムは、修正案の提示が難しいためコメントのみに留めたい 将来的に構文解析とのハイブリッド構成で精度向上を目指す 次に、完成した仕組みを紹介します。 完成した仕組み デモ動画 PR 作成後に GitHub Actions が実行され、生成 AI がレビュー結果を PR のコメントとしてフィードバックします。実際の処理時間は約 1 分40 秒ですが、動画では待ち時間を省略しています。なお、生成 AI のコストは Claude 3.5 Sonnet を用いた場合、 DDL を 1 つレビューするために約 0.1 USD 必要という試算になりました。 https://www.youtube.com/watch?v=bGcXu9FjmJI アーキテクチャ 最終的なアーキテクチャは下図の通りです。なお、使用するプロンプトをチューニングするための評価アプリケーションは別途構築しており、詳細は後述します。 処理の流れ PR のオープンをトリガに GitHub Actions ワークフローを実行し、AWS Step Functions を起動します。この際、PR の URL とワークフロー内で生成した GITHUB_TOKEN を DynamoDB に保存します。DDL を直接 Step Functions に渡さないのは、入力文字数制限を回避するためです。PR の URL を元に Lambda 側で DDL を抽出します。Step Functions は Map ステート を利用し、各 DDL を並列でレビューします。1 回のレビューでチェックするガイドラインは 1 項目だけです。複数のガイドライン観点でレビューするために、最初のプロンプトで得た「修正後の DDL」を次のプロンプトに渡す処理を繰り返し、最終的な DDL を生成します(理由は後述)。レビュー完了後、PR にコメントとしてフィードバックします。レビュー結果は S3 に保存され、LLM-as-a-Judge を用いて生成 AI がその結果を評価します(詳細は後述)。 結果例を以下に図示します。 生成 AI からのフィードバックとして「適用したガイドライン」と「修正案」がコメントされます(画像左)。詳細は折りたたまれており、展開すると DDL への具体的な修正内容やコメントが確認できます(画像右)。 導入に必要な手順 以下の 2 ステップでテーブル設計レビュー機能の導入が完了します。数分で設定できるため、簡単に導入して即座に生成 AI によるレビューを開始できます。 AWS リソースへのアクセスに必要なキーを GitHub Actions Secrets に登録 対象の GitHub リポジトリにレビュー機能用の GitHub Actions ワークフローを追加 DBRE が提供するテンプレートファイルにプロダクト名を追記するだけ 次に、実装で工夫した点を何点か紹介します。 実装時の工夫点 コンテナイメージと Step Functions の活用 当初はシンプルに Lambda のみで実装予定でしたが、以下の課題がありました。 使用するライブラリのサイズが大きく、Lambda のデプロイパッケージサイズ制限(250 MB)を超える 複数のガイドライン観点を Chain して評価する場合、Lambda の最大実行時間(15分)に達してしまう懸念 DDL を直列に処理すると、DDL の数が増えるほど実行時間が長くなる 1 を解決するために Lambda にコンテナイメージを採用しました。また、2 と 3 を解決するため Step Functions を導入し、1 回の Lambda 実行で 1 つの DDL を 1 つのガイドライン観点で評価する設計に変更しました。さらに、Map ステートを使い DDL ごとに並列処理を行うことで、全体の処理時間が DDL の数に影響されないようにしました。下図は Map ステートの実装を示しており、ループ部分でプロンプトの Chain を実現しています。 Bedrock API のスロットリング対策 レビュー時に DDL の数 x ガイドラインの数 だけ Bedrock の InvokeModel リクエストが発生し、クォータ制限によるエラーが発生することがありました。 AWS のドキュメント によると、この制限は緩和できません。そのため、DDL 単位でリクエストを複数リージョンに分散し、エラー時はさらに別のリージョンでリトライする仕組みを導入しました。これにより、RateLimit に達することがほぼなくなり安定したレビューが可能になりました。 ただし、現在は クロスリージョン推論 を利用することで複数リージョン間でトラフィックが動的にルーティングされ、スロットリング対策は AWS 側に任せることが可能なため、今後はこちらに移行予定です。 Lambda から GitHub API を実行するための権限付与方法の整理 Lambda で「対象 PR の変更ファイルの取得」と「対象 PR へのコメント投稿」を実現するため、以下の 3 種類の権限付与方法を比較検討しました。 トークン種別 有効期限 メリット デメリット Personal Access Token 設定次第、無期限も可能 権限の適用範囲が広い 個人への依存 GITHUB_TOKEN ワークフロー実行中のみ 取得が簡単 対象の処理次第で権限不足の懸念 GitHub App(installation access token) 1 時間 GITHUB_TOKEN で未対応の権限も付与可能 プロダクトへ導入する際の手順の複雑化 今回は以下の理由から GITHUB_TOKEN を採用しました。 トークンは短期間(ワークフロー中のみ)有効で、セキュリティリスクが低い トークンの発行・管理が自動化され、運用負荷が低い 今回の処理に必要な権限を付与可能 トークンは有効期限(TTL)付きで DynamoDB に保存し、必要なときに Lambda から取得して使用します。これにより、トークン受け渡し処理のログへの記録有無を調査する必要がなく、安全にトークンを利用できます。 以降では、生成 AI アプリケーションの評価事例について紹介します。 生成 AI アプリケーションの評価 生成 AI アプリケーションの評価について、 Microsoft 社のドキュメント に記載の下図を参考にしました。 出典: Microsoft 社 - 生成 AI アプリケーションの評価 この図によると、GenAIOps(今回は LLM が対象のため LLMOps)ライフサイクルの中で実施すべき評価は 3 種類です。 モデル選定フェーズ 基盤モデルを評価し、使用するモデルを決定 アプリケーション開発フェーズ アプリケーションの出力(≒ 生成 AI の応答)を、品質・安全などの観点で評価しチューニングを実施 デプロイ後の運用フェーズ Production 環境へデプロイ後も、品質・安全性などについて継続的な評価を実施 以降では、各フェーズにおける評価をどのように実施したか、事例を紹介します。 モデル選定フェーズにおける評価 今回は Amazon Bedrock の基盤モデルから選定を行い、 Chatbot Arena のスコアと 社内の生成 AI 有識者 のアドバイスをもとに評価し、Anthropic 社の Claude を採用しました。着手時点で最も高性能だった Claude 3.0 Opus を用いて DDL のレビューを実施し、一定の精度を確認しました。モデルごとにベースの性能やレスポンス速度、金銭的コストが異なりますが、今回はレビュー発生頻度は高くなく、かつ「できる限り高速に」といった要件はないため、性能を最も重視したモデル選定を実施しています。あとは Claude のベストプラクティスに基づきプロンプトチューニングを行うことで、さらに高い精度が期待できると判断し、次のフェーズへ進みました。 なお、途中でより高性能・高速な Claude 3.5 Sonnet がリリースされ、推論の精度はさらに向上しました。 アプリケーション開発フェーズにおける評価 生成 AI の評価手法については、 こちらの記事 に分かりやすくまとめられています。記事内で、 プロンプト、基盤モデル、RAGの有無で、さまざまな評価のパターンが考えられます。 と述べられているように、「何を評価するか」によって評価のパターンが異なります。今回は「単一のプロンプト」の評価に焦点を当て、「DB のテーブル設計を社内独自のガイドラインに従いレビューさせる」という特定のユースケースで、評価の設計と実装の具体例を紹介します。 プロンプトチューニングと評価の流れ プロンプトチューニングと評価は Claude のドキュメント に記載の下図に沿って実施しました。 出典: Anthropic 社 - Create strong empirical evaluations ポイントは、「プロンプトの実行結果が期待にどれだけ近いか」などの評価観点を「何らかの方法で算出したスコア」として定義し、最良のスコアを得たプロンプトを採用する点です。評価の仕組みがない場合、チューニング前後の精度向上を判断する際に主観に頼りがちになり、結果として曖昧さや作業時間の増加を招く恐れがあります。 以降では、まず生成 AI の評価手法について、その後にプロンプトチューニング事例を紹介します。 「生成 AI の評価」とは deep checks という生成 AI 評価用プロダクトのページには、評価に関して以下の記載があります。 Evaluation = Quality + Compliance これは生成 AI アプリケーションの評価を最も端的に表現していると感じました。さらに細分化すると、 こちらの記事 ではサービス提供者の評価観点として「真実性、安全性、公平性、堅牢性」の 4 つの観点に分類されています。評価の観点とスコア算出方法は、アプリケーションの特性に応じて選ぶ必要があります。たとえば、 Amazon Bedrock では、テキスト要約に「BERT スコア」、質問回答には「F1 スコア」など、タスクごとに異なる指標が使われています。 評価スコアの算出方法 anthropic-cookbook では、スコアの算出方法を以下の 3 つに大別しています。 anthropic-cookbook に記載のスコア算出方法まとめ スコアの計算ロジックは、クラウドサービスや OSS を利用するか、自作するかを選択します。いずれにせよ、評価基準は自分たちで設定する必要があります。例えば LLM の出力が JSON 形式の場合、「文字列の完全一致」より「各要素単位の一致」が適切な場合もあります。 Model-based grading について、 anthropic-cookbook に記載のコードをより簡潔に表現すると以下のようになります。 def model_based_grading(answer_by_llm, rubric): prompt = f"""<answer>タグの回答を、<rubric>タグの観点で評価して。「正しい」または「正しくない」と回答して。 <answer>{answer_by_llm}</answer> <rubric>{rubric}</rubric> """ return llm_invoke(prompt) # 作成したプロンプトをLLMに渡して推論させる rubric = "正しい回答は、2 種類以上のトレーニングプランを含んでいる必要があります。" answer_by_llm_1 = "おすすめのトレーニングは、「腕立て伏せ」「スクワット」「腹筋」です。" # 実際はLLMの出力 grade = model_based_grading(answer_by_llm_1, rubric) print(grade) # 「正しい」と出力されるはず answer_by_llm_2 = "おすすめのトレーニングは、「腕立て伏せ」です。" # 実際はLLMの出力 grade = model_based_grading(answer_by_llm_2, rubric) print(grade) # 「正しくない」と出力されるはず 評価についてのまとめ ここまでの内容をまとめると、評価手法は以下の図で表現できます。 生成 AI の評価は、抽象的には Quality と Compliance に分解されます。これらをさらに細分化し、ユースケースごとに自分たちで具体的な評価観点を設定します。各観点は数値化が必要で、その手段は「Code」「Human」「Model」のいずれかをベースに実現します。 以降では、「DB のテーブル設計レビュー」という観点での具体的な評価方法について説明します。 DB のテーブル設計レビューにおける評価設計 Quality の評価は以下の理由で Code-based アプローチを選択しました。 Human による評価とチューニングのサイクルは工数増大につながり、得られるメリットに見合わない Model-based も検討したが、正解 DDL との完全一致をベストスコアにしたく、Code-based の方が適切と判断 正解データとの「DDL における類似度」を新たに実装するのは難しいため、テキスト間の距離を計測する手法の 1 つであるレーベンシュタイン距離(Levenshtein Distance)をスコアの算出方法に採用しました。この手法では、完全一致時の距離は 0 となり、値が大きいほど類似度が低くなります。ただし、「DDL における類似度」を完全に表す指標ではないため、基本的には全データセットで 0 スコアを目指し、非 0 スコアのデータセットに対してプロンプトチューニングを行う方針としました。アルゴリズムは LangChain の String Evaluators(String Distance) でも提供されており、こちらを使用しています。 一方、Compliance の観点については社内向けアプリケーションであることや、プロンプトに埋め込むユーザー入力を DDL に限定する実装になっていることから、今回は不要と判断しました。 評価の実装 実装した評価の流れは以下のとおりです。 各レビュー観点ごとに、入力用 DDL と正解 DDL を組み合わせた 10 パターンのデータセットを作成しました。効率的にプロンプトチューニングと評価を繰り返すため、Python と Streamlit で専用アプリケーションを開発しました。データセットは jsonl 形式で保存し、ファイルを指定すると自動で評価を実行して結果が表示されます。以下のように各 json は「評価対象のガイドライン」「LLM を Invoke する際のパラメータ」「入力 DDL」「正解 DDL」が含まれています。 { "guidline_ids": [1,2], "top_p": 0, "temperature": 0, "max_tokens": 10000, "input_ddl": "CREATE TABLE sample_table (...);", "ground_truth": "CREATE TABLE sample_table (...);" } 個別の結果表示では、出力 DDL と正解 DDL の diff を表示し、視覚的に差異(=チューニングポイント)が分かるよう工夫しました。 評価が全て終わると、スコアの集計結果も確認できます。 プロンプトチューニング Claude のドキュメント に基づき、以下のポイントを意識してプロンプトの作成・チューニングを行い、最終的に 60 個のデータセットほぼ全てにおいて、最良(0スコア)の結果を達成しました。 ロールを設定 XML タグを活用 Claude に思考させる(思考過程の出力を指示することで、期待外れの回答時にデバッグを容易に) Few-Shot Prompting(出力例を提示) 参照データを冒頭に、指示を末尾に配置 明確かつ具体的な指示を与える プロンプトを Chain させる 有名な手法も多く詳細は省略しますが、最後の 2 点について補足します。 「明確で具体的な指示を与える」 当初は社内のテーブル設計ガイドラインの文章をそのままプロンプトに埋め込んでいました。しかし、ガイドラインには「あるべき姿」のみ記載され、「具体的な修正手順」が含まれていませんでした。そこで、Step-by-step 形式の「具体的な修正指示」に書き換えました。例えば「真偽値を格納するカラム名に xxx_flag を使用しない」というガイドラインについては、以下のように修正しました。 以下の手順に従い、真偽値が格納されているカラム名を抽出し、必要に応じて適切なカラム名に変更してください。 1. 真偽値が格納されているカラム名を抽出します。判断基準は、boolean型が使われているか「flag」文字が入っているかです。 2. 真偽値カラムの名前を1つずつチェックし、カラムの意味をまずは理解します。 3. 真偽値カラムの名前を1つずつチェックし、より適切な名前があると判断した場合は、カラム名を修正します。 4. 適切なカラム名の条件は<appropriate_column_name></appropriate_column_name>タグの中を参照してください。 <appropriate_column_name> 「flag」という文字を使わず... ... </appropriate_column_name> 「プロンプトを Chain させる」 チェックするガイドラインが増えるほど、1 回で全てを確認しようとするとプロンプトが複雑化し、チェック漏れや精度低下の懸念が高まります。そのため、1 回のプロンプト実行で AI にチェックさせる項目を 1 つに限定しました。最初のプロンプトで得た「修正後の DDL」を次のプロンプトの入力として渡し(Chain)、繰り返し処理することで最終的な DDL を得る仕組みにして、アーキテクチャにも反映しました。プロンプトの Chain で、以下のメリットも得られました。 プロンプトが短く、タスクが 1 つに絞られるため精度が向上 ガイドライン追加時は新規プロンプトを作成するだけでよく、既存プロンプトへの精度影響がない 一方で、LLM の Invoke 回数が増えるため、時間と金銭的コストは増加します。 デプロイ後の運用フェーズにおける評価 プロンプト作成段階では、手動で作成した正解データを用いて Quality を評価しました。しかし、Production 環境では正解データが存在しないため、別の評価手法が必要です。そこで「LLM の応答を LLM に評価させる」手法である LLM-as-a-Judge を採用しました。 Confident AI のドキュメント によると、この手法には 3 種類の方法があるとされています。 Single Output Scoring (正解データなし) 「LLM の出力」と「評価基準(Criteria)」を与え、基準に基づき LLM にスコアを付けさせる Single Output Scoring (正解データあり) 上記に加え「正解データ」も提供。より高精度な評価が期待できる Pairwise Comparison 2 つの出力を比較し、どちらが優れているかを判定。「優れた」の Criteria は自分で定義する 今回は Single Output Scoring (正解データなし) を採用しました。この手法も LangChain でサポート されており、提供されている関数を使用しました。なお、現在は LangSmith による実装が推奨されています。 評価基準(Criteria)は以下の 2 つを定義し、それぞれ 10 点満点で採点させています。 Appropriateness LLM の出力がガイドラインに沿って適切に修正されているか Formatting Consistency 不要な改行や空白などが付与されておらず、フォーマットの一貫性が保たれているか コードとプロンプトのイメージは以下の通りです。 input_prompt =""" <input_sql_ddl>CREATE TABLE ...</input_sql_ddl> <table_check_rule>曖昧なオブジェクト名は...</table_check_rule> 指示書:table_check_ruleを元に、input_sql_ddlを適切なDDLに修正してください。 """ output_ddl = "CREATE TABLE ..." # 実際はLLMが生成したDDLをセット appropriateness_criteria = { "appropriateness": """ Score 1: ... ... Score 7: 入力の指示に概ね従った応答が生成されており、不適切な修正が2箇所以下である。 Score 10: 入力の指示に完全に従った応答が生成されている。 """ } evaluator = langchain.evaluation.load_evaluator( "score_string", llm=model, criteria=appropriateness_criteria ) result = evaluator.evaluate_strings( prediction=output_ddl, input=input_prompt ) print(result) この実装で以下のような出力が得られます。(省略箇所あり) この回答は、与えられた指示に完全に従っています。以下に評価の理由を説明します: 1. カラム名の抽出と評価: 回答者は全てのカラム名を抽出し、それぞれのカラム名がデータの中身を推測できるかどうかを適切に判断しています。 2. 曖昧なカラム名の特定: 提供されたDDL内のカラム名は全て、その目的や格納されるデータの種類を明確に示しています。例えば、... ... この回答は、与えられた指示を完全に理解し、適切に実行しています。 Rating: [[10]] この仕組みは、アーキテクチャ図における下図赤枠部分に相当します。 LLM のレビュー結果が S3 に保存されると、SQS を介して非同期で LLM-as-a-Judge 用の Lambda が起動します。この Lambda が評価を実行し、結果はログとして S3 に保存され、スコアは CloudWatch のカスタムメトリクスとして送信されます。また、CloudWatch Alarm により閾値を下回る場合は Slack に通知されます。 このスコアは完全に信頼できるものではありませんが、対象が社内向けシステムであるため、ユーザーからのフィードバックを得やすい環境です。そのため、定量的なスコアで継続的にモニタリングしつつ、定期的にユーザーフィードバックを収集する体制をとりました。 得られた学びと今後の展望 最後に、生成 AI アプリケーション開発に挑戦して得た学びと、今後の方向性についてまとめます。 評価が非常に重要かつ難しい プロンプトの結果を同じ観点で評価することで、主観を排除しつつ高速にチューニングと評価を繰り返すことができました。この経験から、評価設計の重要性を強く感じました。ただし、GenAIOps における3つの評価(モデル選定時、開発時、運用時)はユースケースごとに判断が必要で「自分たちの評価設計の妥当性判断」は難しいと感じました。また、評価の観点が不足していると、コンプライアンス面で問題を抱えたアプリケーションを提供するリスクもあります。今後、より体系的でマネージドな評価方法や仕組みが提供されれば、GenAIOps が実現しやすくなっていくと考えます。 生成 AI のユースケースの想像の幅が広がった 実際に自分たちで生成 AI アプリケーションに関して調査・実装したことで解像度が上がり、ユースケースの想像の幅が広がりました。例えば、 エージェント と ロック競合の情報を収集する仕組み などを組み合わせて、よりマネージドかつ高速にインシデント調査ができる仕組みなどを想像できるようになりました。 プログラマブルなタスクの代替としての 生成 AI 活用 生成 AI をアプリケーション開発に活用する方法は、大きく次の 2 つに分けられます。 タスクそのものを 生成 AI に実施させる プログラム開発の生産性を 生成 AI で向上させる 今回は、 生成 AI に推論させるべきタスクだけでなく、本来はプログラムで処理すべきタスクも生成 AI を使って実装しました。当初は「プロンプトを工夫すれば迅速に高精度の結果が得られるかもしれない」と期待していましたが、実際にはプロンプトチューニングに多くの時間が必要であると痛感しました。一方、Claude の複数モデルで同じタスクを解かせた結果、モデルの精度が高いほど明確に結果が改善されることを確認しました。さらに、精度が高いモデルでは予測不能な挙動が減り、プロンプトチューニングに要する時間も短縮できることが分かりました。 これらの経験を踏まえると、今後モデル精度がさらに向上した場合、要件次第では「タスクを実施するプログラムを書く」代わりに「タスクそのものを 生成 AI に実施させる」という手段を選択するケースが増えるかもしれません。 今後の展望 今後は以下のようなことに取り組んでいきたいと考えています。 対応ガイドラインの拡充 導入プロダクトの拡大 プログラムによる構文解析とのハイブリッド構成にする レビュー結果をユーザーへフィードバックする際の「分かりやすさ」の改善 現状の簡易的な LLMOps を、モニタリングだけでなくログを活用したプロンプト・モデル改善にまで拡張 参考: @hiro_gamo 氏の Post まとめ 本記事では、Amazon Bedrock を活用し、Serverless アーキテクチャで実装した DB テーブル設計の自動レビュー機能を紹介しました。また、生成 AI アプリケーションの評価方法についても解説しました。先日開催された AWS AI Day でのセッション「 コンテンツ審査を題材とした生成AI機能実装のベストプラクティス 」で紹介されている「 LLM の実導⼊における考慮点と打ち⼿ 」の各項目にマッピングする形で、今回の取り組みを以下にまとめます。 項目 内容 他の手段との棲み分け ● DBRE チームで LLM による自動化に挑戦 ● 将来的にはルールベースとの併用を目指す 精度 ● 「テーブル設計レビュー」というユースケースに沿った評価観点を設計 ● プロンプトチューニングと評価を高速に繰り返すために、専用のアプリケーションを開発 ● 選定したモデル(Claude)のベストプラクティスに従ってプロンプトチューニングを実施 ● 1 回のプロンプト実行で AI にチェックさせる項目を 1 つに限定 & プロンプトの Chain で精度向上 コスト ● DDL の文字数にもよるが、1 DDL あたり約 0.1 USD ● レビュー発生頻度は低いため、コストより精度を重視したモデル選定(Claude 3.5 sonnet) ● プロンプトの Chain も同様に、コストは増えるが精度向上のメリットが大きいと判断 可用性・スループット ● クォータを意識してリージョン間のリクエスト分散やリトライ処理を実装 ● よりマネージドな クロスリージョン推論 へと移行予定 レスポンス速度 ● 「できる限り高速に」といった要件はないため、速度よりも精度を優先したモデル選定 ● 各 DDL を並列でレビューすることで速度向上 ● 数 10 個 の DDL に対して 2-5 分程度で レスポンスを返却 LLMOps ● LLM-as-a-Judge を用いて継続的に精度をモニタリング セキュリティ ● GitHub との連携には、GHA ワークフロー実行中だけ有効な GITHUB_TOKEN を採用 ● 社内アプリ & 入力が DDL に限定されるため、LLM の応答への Compliance 観点評価は未実施 本プロダクトは現在複数のプロダクトへの導入が進行中で、今後もユーザーフィードバックを基に改善を進める予定です。生成 AI アプリケーションは Amazon Bedrock Prompt フロー など開発用のサービスも進化を続けており、今後もより便利になっていくと思います。今後も積極的に生成 AI 領域にも挑戦していきたいです。 KINTO テクノロジーズ DBRE チームでは一緒に働いてくれる仲間を絶賛募集中です!カジュアルな面談も歓迎ですので、 少しでも興味を持っていただけた方はお気軽に X の DM 等でご連絡ください。併せて、 弊社の採用 X アカウント もよろしければフォローお願いします!
アバター
Introduction Hello everyone! This is Mori from the Global Development Division and the Tech Blog Operations Team. KINTO Technologies currently has bases in Tokyo, Nagoya, and Osaka, as well as two offices in Tokyo: at Muromachi (Nihonbashi) and Jinbocho. The Global Development Division, which I belong to, sits at the Jinbocho Office. In this article, I'll cover the information sharing meetings held twice at the Jinbocho Office, also known as Jinbocho ISM (Information Sharing Meeting) . *On a side note, some members thought it was read as "Jinbochoism," which I thought was a nice way to capture our unique Jinbocho style. Background It has been a year since the Jinbocho Office opened in June 2022, and the number of groups and new employees has increased to a whopping 100. However, we still often hear comments like, "I have no idea what other teams or groups are working on," or "I don't really know who's working here." With that in mind, we thought, "Why not host an information-sharing meeting at the Jinbocho office, like the ones held at the Osaka Tech Lab ?" Driven by this idea, the Tech Blog Operations Team led by team members from the Jinbocho Office, dove right in and planned the event with full enthusiasm! The 1st Jinbocho ISM Held on June 23, 2023! For the first meeting, we decided to start small! So, we kept it to 30 minutes and designed a simple agenda as shown below. Opening (5 min) Ask me anything* (20 min) Closing: Survey (5 min) What is an Ask Me Anything? An Ask Me Anything, commonly abbreviated as AMA, is a format where a host or guest invites people to ask them any questions, often starting with 'I'm ○○, feel free to ask me anything.' It is a format that allows people to ask anything to particular hosts or guests, mainly on social media. Popular on social media, AMA sessions allow participants to ask anything—from professional background and current work to personal topics. You can find plenty of examples of AMA sessions online, so feel free to explore 😄 Reference: What is AMA? Understanding the Basics of Ask Me Anything Since an AMA was included in the plan, we started by selecting people to answer the questions. For the first meeting, we invited the Group Manager of the Global Development Division. However, with only one available day, we had to prepare, communicate, and hold the meeting within about two weeks. Despite the short preparation time, around 30 people participated, and we received positive feedback from them. Here are some of their comments: I really appreciated having a space to hear from people I don’t usually get to talk to, learning about their backgrounds, hobbies, and other topics beyond work. It was very interesting to hear about the early days of KINTO, especially since there are few remaining documents about that time. I'd like to hear more stories from different team members. [1st post-event survey] would you like to participate again next time? On the other hand, since AMAs mainly involve asking questions and listening to one person, they can lack a sense of full participation for everyone. We held a retrospective to discuss how we could create a more lively and engaging atmosphere as the next challenge for the operations team. As for how we approached the retrospective and planned the next meeting, we employed various methods, but I'll save those details for another time. The 1st meeting photo The 2nd Jinbocho ISM The 2nd Jinbocho ISM, planned as such, was held on August 25, 2023, with an extended duration of one hour. The agenda was as follows: Opening (5 min) Talk with Neighbors (25 min) 🆕❗ Ask Me Anything (25 min) Closing (5 min) The AMA was so well-received that we decided to keep the format and invite Kamei-san from the Woven Payment Solution Development Group as the guest speaker. To encourage more lively participation from everyone, the operations team introduced a section called Talk with Neighbors . We gave the title in English to make it sound cool, but it's simply a segment where participants are divided into teams of 4-5 people for a casual conversation. Since free talk with people meeting for the first time can benefit from some ice-breaker topics, we adopted a dice (a.k.a. Korosuke) to provide prompts. Dice? Free talk? Yes, I was referencing a popular Japanese program, likely familiar to those who grew up during the Heisei era lol During the planning process, I was concerned about whether the teams would be able to start conversing easily after being divided in teams. However, I was impressed by how naturally the teams began to talk, making my worries unnecessary. When people ran out of topics to discuss, they rolled the dice and talked about a new theme✨ *Divided into teams, full of lively chatter. Do you see that? * The AMA was held in the second half hour. More people asked questions than in the previous meeting, partly because the Woven Payment Solution Development Group usually works away from KINTO. Some people said, "I wanted to hear more!"but as time is limited during the information-sharing meeting, we hope this gave more people the opportunity to interact with other teams outside of this session✨ [2nd post-event survey] would you like to participate again next time? Around 30 members participated in this next session, roughly the same number as the previous one and we received positive feedback such as: "It was good to be able to talk to people across divisions," "It was a great opportunity to get to know people who work in the same office," "I was able to interact with colleagues I don't usually interact with, and I look forward to the next one!"The number of people saying, "I'd like to participate again next time" exceeded that of the previous meeting.✨ Conclusion The Jinbocho ISM has been held twice so far, and overall, it has become clear that everyone values opportunities for cross-team interaction. While at first glance, these gatherings might seem unrelated to work, it can spark ideas like, "I know this person knows a lot about this, maybe I can ask for advice," which can ultimately benefit your work. Although we've only had two sessions so far and there is still room for improvement, we plan to continue holding them regularly to deepen interactions among members and energize the entire Jinbocho Office.
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の12日目の記事です🎅🎄 はじめに みなさんこんにちは!KTC(KINTOテクノロジーズ)にてプラットフォームグループMSPチームに所属しているマツノです(入社時エントリーは こちら )。 前職ではSES事業会社に在籍し、インフラエンジニアとしてオンプレやAWSに構築されているシステムの保守・運用を担当していました。もう少しシステムやそこに関わる人達と深く関わりたいなと考えていたところ、縁あってKTCにジョインし本日に至ります。 この記事を書こうと思ったきっかけ 突然ですが、みなさんはMSPと聞いてどんなものをイメージしますか?なんの略称かご存じですか? 恥ずかしながら、自分はリクルーターの方に求人票を見せてもらうまでMSPという単語を知りませんでした…。 そこで、この記事では自分がKTCにMSPチームとして入社してから学んだことや苦労したことを紹介しながら、KTCでのMSPチームの取り組みを紹介したいと思います! MSPとは? まずはMSPについて一般的な説明をさせてください。MSPとは「Managed Service Provider」の略称でGartner社の公開している 用語集 では以下のように紹介されています。(原文をDeepLにて翻訳) マネージド・サービス・プロバイダー(MSP)は、ネットワーク、アプリケーション、インフラ、セキュリティなどのサービスを、顧客の構内、MSPのデータセンター(ホスティング)、またはサードパーティのデータセンターで、継続的かつ定期的なサポートと能動的な管理を通じて提供する。 MSPは、自社固有のサービスを他のプロバイダーのサービスと組み合わせて提供することもある(例えば、セキュリティMSPがサードパーティのクラウドIaaSの上でシステム管理を提供するなど)。ピュアプレイMSPは、1つのベンダーやテクノロジーに特化し、通常は自社のコア・サービスを提供する。多くのMSPは、他のタイプのプロバイダーのサービスを含んでいる。 MSPという用語は、従来はインフラやデバイスを中心としたタイプのサービスに適用されていたが、現在では継続的、定期的な管理、メンテナンス、サポートを含むようになった。 Gartner Glossary: Managed Service Provider (MSP)より引用 「継続的かつ定期的なサポートと能動的な管理を通じて提供する。」 ここら辺がMSPの根幹となる考え方になってきます。 MSPという用語を調べてみると事業所によって若干の違いはあるようですが、多くは稼働中のシステムの保守・運用・監視を専門に担当するサービスのことを指すようです。 KTCでのMSPチームの取り組み MSPチームの成り立ち ここからはKTC社内でのMSPチームの取り組みなどを紹介させていただきます! まず、KTCでのMSPチームのミッションですが、 「アプリケーション運用サポートにより間接的な開発スピードと品質向上に貢献する」 となっています。 なぜそのようなミッションになったのかを理解するために、MSPチーム発足当時のことを調べてみました。 MSPチームの構想が立ち上がった当初、KTCでは以下のような課題がありました。 開発中のシステムにおいて開発者と運用者が同一のため、運用作業に追われて開発スピードが上がらない。 障害対応において、システム稼働時間と同等の時間でサポート体制が取れていない。 こういった課題を解決するために、KTCにおけるMSPチームは以下の2チームで構成されています。 MSPサービスデスク(アウトソーシング) MSP内製チーム MSPサービスデスクはアウトソーシングしており、いわゆる24h365d対応の部隊になります。 一方でMSP内勤チームは2023年5月に発足した比較的新しいチームで、平日の日中帯にKTC社内の各開発チームより引き継いだ業務を対応しています。 MSPチームの具体的な業務内容 KTCでは日々様々な内製プロダクトが開発されており、MSPチームでは主に以下のような業務対応をしています。 アカウント管理 アカウント登録・削除 パスワードリセット アカウント棚卸 離職者対応 セキュリティレポート集計・周知対応 データ連携バッチ失敗時のリラン対応 システム監視アラート1次対応 各種問い合わせ対応 上記のリストからイメージ出来る方もいらっしゃるかもしれませんが、 現在のMSPチームではシステム管理・運用において定型的に対応できるもの、 複数のチームで対応する必要があるが対応方法が統一されていなかったもの、 もしくは対応できていなかったものを中心に対応しています。 実際にどんなことをやっているの? いまいちイメージが湧かないと思うので、MSPチームで取り組んでいるとある業務の詳細を紹介していきます。 現在、MSPチームでは毎月社内向けに公開される人事情報を元に、離職者対応というものを実施しています。 業務内容としては退職や育児休暇等で離職される社員の情報をとりまとめ、対象システム(内製・SaaS含む)のアカウント有無の確認から削除までを一括対応するというものです。2024年11月時点では2つのグループが開発・管理している、合計7つのシステムに関して離職者対応を実施しています。 この業務をMSPチームが一括で対応することのメリットは、例えば以下のようなことが挙げられると思います。 業務の標準化 グループ間やシステム間でのアカウント管理の差異をなくせる。 運用コスト削減と開発への注力 システム開発・管理を担当しているチームの運用コストを減らせる。 業務の属人化防止 MSP内製チームにて手順書を作成し、チームメンバー全員が対応できる状態を維持する。 運用コスト削減については、ここで例に挙げている離職者対応について具体的な計算をしてみます。 7つのシステムについて担当者が月次で離職者アカウントの削除対応しているとしましょう。そしてそれぞれの作業が大体2時間程度掛かると仮定します。 そうすると全体で必要な月次・年次運用コストは 2(月次の工数) × 7(対象システム数) = 14(時間/月) 14(時間/月) × 12 = 168(時間/年) となります。あくまで概算ですが、年間で約150時間程度の工数を開発チームの代わりにMSPチームが担当しているということがわかりますね。 自分がKTCにジョインしたタイミングで、すでに離職者対応はMSPチームにて対応していたのですが、月半ばであっても離職者の方が離職された翌稼働日にアカウント削除しており、かなりきっちり対応しているなという印象を受けたのを覚えています。 こういったメリットがある一方で、当然デメリットもあります。 以下のようなものが考えられるでしょう。 コミュニケーションコストの肥大化 業務によっては引継ぎ元チームとのやりとりが増え、業務を手放した恩恵を感じにくい。 引継ぎリスク MSPチームの作業ミスによって、リカバリー対応等を求められる。 これらメリット・デメリットを踏まえ、 業務を引き継ぐタイミングで如何にデメリットを最小限に抑え、メリットを増やしていくのかが腕の見せ所になります。 ここまではKTCでのMSPチームの成り立ちや、具体的な業務の紹介をさせて頂きました。 かっこいいことも書きましたが、自分はまだまだ未熟者なので日々勉強中です…。 MSPチームのこれから 求められたこと ここからは自分自身がKTCに入社してから求められたことや取り組んだことをお話ししつつ、これからのMSPチームについて紹介させていただいてこの記事を締めようと思います! まず、KTCに入社する際に自分に求められたことは大きく以下の2点でした。 MSP内製チーム現場リーダーとして成長し、内製チームをリードすること。 今まで経験したシステム保守・運用の実務経験を活かし、MSP内製チームの業務拡大に貢献すること。 これらは自分にとってとてもチャレンジングな内容でした。なぜなら、今までの働き方は日次・週次・月次といった各スパンで一定の決められた業務があり、それらをミスなく同じクオリティでアウトプットすることが求められるようなものだったからです。組織としてもシステムとしても安定期にある環境がほとんどでした。 一方でKTCは組織としてもビジネスとしても拡大を狙う組織であり、当然それに伴い新規システムの開発も進んでいます。開発チームの開発スピードと品質向上をミッションに掲げるMSPチームとしては、対応業務の拡大を進めたいということになってきます。 現場リーダーとして意識したこと 前述したように自分に求められたことは理解していたつもりなのですが、1人のエンジニアとして目の前の業務をこなすのと現場リーダーとして立ち回るのとでは全く違いました。 今までは自分自身のアウトプットにのみ気を配ればよかったのですが、現場リーダーとして成長するためには、チームメンバー全員のアウトプットにまで気を配る必要があります。 もちろん自分一人が責任を負う必要はないのですが、どこまで把握する必要があって、どこまでを委ねて良いのかのさじ加減が難しいと感じています。 周りのサポートもありだいぶ慣れてきましたが、日々勉強だなぁと感じています。 MSP内製チームの業務拡大に向けて 最後に自分のシステム保守・運用の実務経験を活かし、MSP内製チームの業務拡大に貢献するという部分ですが、こちらは絶賛悪戦苦闘中です。 過去に組織として縮小傾向にある開発現場にいたことがあるのですが、そこでは業務の属人化が進んでしまっていました。担当者の高齢化や過負荷な状況が続いていたこともあり、属人化を解消するのがかなり難しい状況になっていると感じたのを覚えています。自分なりに出来ることはやったつもりですが、工数も限られているため、出来ることには限界がありました。 そういった経験を踏まえても、今のMSPチームが取り組んでいることはKTCにとって有用性があると感じています。 チームとしてやろうとしていることやその必要性はとても良く理解できる、しかしノウハウが自分の中にないという状況です。 MSPチームの取り組みを拡大するためには、自分自身が以下のようなことが出来るようになる・実践する必要があると考えています。 適切な業務設計・フローとするために、システム観点だけでなく業務観点で考える。 新たな業務を引き継ぐ際には、MSPチームとしてのアウトプットが揃う手順とする。 KTC社内でのMSPチームの取り組みを知ってもらう。 もともと手順書などのドキュメント類を作ることは嫌いではなく、定型作業のようなルーチンワークにも抵抗がないため、それなりに適正はあると自負しているのですが、業務を作る部分が本当に難しいと感じます…。今まで自分自身がシステム開発寄りの働き方をしていたため、思考の癖としてシステムの仕様や内部で利用しているAWSサービスが気になってしまいます。ですが良い業務を作るためにはそれではいけません。「業務観点ってなんだ?」と自問自答しながらドキュメントと向き合う毎日です。 アウトプットを揃えるという観点についても、業務フローを整えチケット起票したうえで、そこに必要な情報を集めてから対応する手順が基本だと理解しています。ですが業務フローを整えるのに四苦八苦しています。ちゃんと考えるべき部分と、あまり考えなくて良い部分の判断が難しいです。 上記のことが自分のスタイルを確立したうえで実践できれば、業務引継ぎのリスクを最小限に抑えメリットを最大化することができると思うのですが、これがなかなか難しい…。一朝一夕で身に付かないものだと感じているので、今は上長やマネージャーに助けてもらいながら日々の業務に取り組んでいます。ただ、自分の働きによって良い業務を作ることが出来れば、それがMSPチーム拡大に繋がるので頑張ろうと思います。 さいごに この記事では一般的なMSPの話から始まり、2024年4月にKTCに入社した筆者の目線から見たMSPチームの取り組みや、有用性、これからやりたいことを紹介させていただきました。 普段の業務で技術的に高度なことに取り組んでいるようなことがないため、他のテックブログと違い技術系の話や、技術的に困ったことを紹介するものではなく、自身の主観を交えながらKTCでのMSPチームを紹介させて頂きました。少しでもMSPチームでの取り組みに興味を持ってもらえたら嬉しいです。
アバター
This article is the 12th day of the KINTO Technologies Advent Calendar 2024 . 🎅🎄 Hello, we are the Android development team at the 'KINTO Kantan Moushikomi App' (KINTO Easy Application App). Today, we want to share the process of implementing Kotlin Multiplatform (KMP) into our existing app, the reasons behind it, and the changes and improvements it has brought. Over the past year, we have been exploring ways to maximize development efficiency between iOS and Android platforms. During this process, KMP caught our attention, and our team would like to share how this technology has innovatively improved our development process. Contents 1. Reasons for Implementing KMP in an Existing App 2. Integrating KMP into Our Existing App 2.1 Deciding on Shared Code Placement 2.2 Organizing the Shared Code 2.3 Creating a KMP Module 2.4 Multi-module Architecture and Umbrella Module 2.5 CI: Testing Shared Code on Android and iOS 3. Distributing Your KMP Code 3.1 Options for Distributing KMP Code 3.2 Swift Package Manager (SPM) 3.3 Automating Distribution 4. Android and iOS Implementation Methods 4.1 Feature Selection 5. Issues in KMP Cross-Platform Module Implementation 6. Effects 7. Moving Forward: Future Plans and Challenges 1. Reasons for Implementing KMP in an Existing App At the time, our team faced a shortage of iOS development resources. To address this challenge, the Android team decided to leverage Kotlin Multiplatform (KMP) to create shared business logic for both iOS and Android platforms. This approach reduced code duplication across operating systems and allowed the Android team to utilize their expertise to support iOS development. This strategy was seen as a crucial solution to alleviate staffing issues and significantly enhance development productivity, which became the decisive reason for integrating KMP technology into our existing app. [Summary of the Background] Addressing the shortage of iOS development resources Leveraging the Android team’s expertise in Kotlin Reducing code duplication across operating systems Improving development productivity and strengthening team collaboration ※ Let's eliminate duplicated efforts across operating systems by modularizing business logic into a KMP library. 2. Integrating KMP into Our Existing App Before implementing any KMP code, we made several strategic decisions about where to place and how to organize our shared code. 2.1 Deciding on Shared Code Placement Our mobile app has a typical setup with two separate development teams: an Android team working on the Android repository and an iOS team working on the iOS repository. When introducing KMP, the first question that arises is: where should the shared code reside? Option 1: Shared Code in a Separate Repository This option involves creating a new repository for the shared code, which can be accessed by both the Android and iOS repositories. The repository structure would look like this: graph TB; subgraph Android Repository AndroidApp end subgraph iOS Repository iOSApp end subgraph KMP Repository KMP end KMP --> AndroidApp KMP --> iOSApp Option 2: Shared Code in the Android Repository In this option, the shared code is placed in the Android repository, allowing the Android team to manage the shared codebase. The repository structure would look like this: graph TB; subgraph Android Repository KMP --> AndroidApp end subgraph iOS Repository KMP --> iOSApp end Option 3: Merge Android and iOS Repositories into a Monorepo This option involves merging the Android and iOS repositories into a monorepo, allowing both teams to access the shared codebase. The repository structure would look like this: graph TB; subgraph One Repository KMP --> AndroidApp KMP --> iOSApp end [Our Decision] After considering the pros and cons of each option, we decided to place the shared code in the Android repository. This decision was based on the following factors: Minimizing the impact on existing workflows Easier to manage the shared codebase 2.2 Organizing the Shared Code Once we decided where to place the shared code, the next decision was how to organize it. Since our existing Android app follows a multi-module architecture, we wanted to maintain a clear separation between the shared module and the platform-specific modules. We decided to place the KMP module in the shared directory within the Android repository, alongside the existing Android modules. for example: :app // Android app module :domain // Android-specific module :shared:api // KMP module :shared:validation // KMP module 2.3 Creating a KMP Module A Gradle module for KMP includes: 1. A build.gradle.kts file. 2. A src subfolder. For Android modules, we apply the com.android.library plugin and include an android {} block: plugins { id("com.android.library") } android { // Android-specific configurations } For KMP modules, we use the multiplatform plugin and define a kotlin {} block: plugins { kotlin("multiplatform") } kotlin { // KMP configurations } This setup allowed us to support both Android- and KMP-specific requirements in our shared codebase. 2.4 Multi-module Architecture and Umbrella Module Limitations of Multiple Modules In Android, splitting code into multiple modules is standard for complex projects. However, KMP currently supports exposing only one module to iOS. For example, suppose we have three modules in our shared codebase: featureA , featureB , and featureC . Each module depends on the data module, which in turn depends on the api module. graph LR; api --> data --> featureA data --> featureB data --> featureC We want to expose these three modules to iOS. In an ideal scenario, iOS developers would import only the modules they need, like so: import featureA import featureB <swift code here> However, due to the limitations of KMP, this approach results in duplicated code in the iOS app. What we want: graph LR; subgraph KMP api --> data --> featureA data --> featureB data --> featureC end featureA --> iOSApp featureB --> iOSApp featureC --> iOSApp What we get (with duplication): graph LR; subgraph KMP api --> data --> featureA end subgraph KMP1 api1(api copy) --> data1(data copy) --> featureB end subgraph KMP2 api2(api copy2) --> data2(data copy2) --> featureC end featureA --> iOSApp featureB --> iOSApp featureC --> iOSApp style api1 fill:#f88 style api2 fill:#f88 style data1 fill:#f88 style data2 fill:#f88 Umbrella Module To work around this limitation, we introduced an umbrella module . An umbrella module is a "empty" module that does not contain source code but used to manage dependencies. graph LR; subgraph KMP subgraph Umbrella api --> data --> featureA data --> featureB data --> featureC end end Umbrella --> iOSApp style Umbrella fill:#8f88 Here is a build.gradle.kts example: kotlin { val xcf = XCFramework() listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { it.binaries.framework { baseName = "Umbrella" binaryOption("bundleId", "com.example.shared") export(project(":shared:featureA")) export(project(":shared:featureB")) export(project(":shared:featureC")) xcf.add(this) } } sourceSets { val commonMain by getting { dependencies { api(project(":shared:featureA")) api(project(":shared:featureB")) api(project(":shared:featureC")) } } } } The umbrella module simplifies the integration process for iOS developers, ensuring a seamless and efficient development experience across platforms. 2.5 CI: Testing Shared Code on Android and iOS We always write tests for our code, and the shared code is no exception. Due to platform differences, some features may not work as expected on iOS. To ensure compatibility, run tests on both Android and iOS. Unlike Android, which can run tests on any OS, iOS tests must be run on macOS. 3. Distributing your KMP code Once you finish writing your KMP code, the next step is to distribute it to iOS app. 3.1 Options for Distributing KMP Code You can distribute your KMP code by source code or binary. Source Code Distribution With source code distribution, iOS developers must compile the KMP code themselves. This approach requires setting up a Kotlin build environment, including tools like Java VM and Gradle. Challenges: Every iOS developer needs to configure the KMP build environment. This increases the complexity of onboarding KMP code into the iOS project. Binary Distribution A better option is binary distribution. By providing precompiled libraries, we eliminate the need for iOS developers to manage an additional build environment, making it much easier to integrate shared code. Advantages: Reduces setup effort for iOS developers. Ensures consistent builds across environments. 3.2 Swift Package Manager (SPM) For iOS, there are two main dependency management systems: CocoaPods and Swift Package Manager (SwiftPM). The choice depends on your iOS team’s preferences. Fortunately, our iOS team has fully transitioned to SwiftPM, so we only need to support SwiftPM. What is a Swift Package? A Swift Package is essentially a Git repository that includes: Swift source code. A Package.swift manifest file. Semantic versioning via Git tags. Binary Distribution with SwiftPM Since SwiftPM 5.3, it has supported binaryTarget , allows you to distribute precompiled libraries instead of source code. Creating a Swift Package with Binary Distribution Here’s a brief explanation of how we publish KMP code as a Swift Package: Compile the KMP code into an .xcframework . Package the .xcframework into a zip file and calculate its checksum. Create a new release page on GitHub and upload the zip file as part of the release assets. Obtain the zip file’s URL from the release page. Generate the Package.swift file based on the URL and checksum. Commit the Package.swift file and add a git tag to mark the release. Associate the git tag with the release page and officially publish the GitHub release. For detailed instructions, refer to the [KMP documentation on Remote SPM export].( https://kotlinlang.org/docs/native-spm.html ) // swift-tools-version: 5.10 import PackageDescription let packageName = "Umbrella" let package = Package( name: packageName, platforms: [ .iOS(.v13) ], products: [ .library( name: packageName, targets: [packageName]), ], targets: [ .binaryTarget( name: packageName, url: "https://url/to/some/remote/xcframework.zip", checksum: "The checksum of the ZIP archive that contains the XCFramework." ] ) 3.3 Automating Distribution Manual distribution can be time-consuming. To streamline the process, we created a GitHub Actions workflow for automation. name: Publish KMP for iOS on: workflow_dispatch: inputs: release_version: description: 'Semantic Version' required: true default: '1.0.0' env: DEVELOPER_DIR: /Applications/Xcode_15.3.app jobs: build: runs-on: macos-14 steps: - name: Checkout uses: actions/checkout@master - name: set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'zulu' - name: "Build and Publish" env: RELEASE_VERSION: ${{ github.event.inputs.release_version }} GH_TOKEN: ${{ github.token }} run: ./scripts/publish_iOS_Framework.sh $RELEASE_VERSION #!/bin/sh set -e MODULE_NAME="<your module name>" VERSION=$1 # version name for github release RELEASE_VERSION="$MODULE_NAME-$VERSION" # tag name for git tag TAG="$VERSION" TMP_BRANCH="kmp_release_$VERSION" # check if VERSION is in semver format if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "VERSION should be in semver format like 1.0.0" exit 1 fi ZIPFILE=./shared/$MODULE_NAME/build/XCFrameworks/release/$MODULE_NAME.xcframework.zip echo "Building $MODULE_NAME $VERSION" ./gradlew assembleKintoOneCoreReleaseXCFramework echo "creating zip file" pushd ./shared/$MODULE_NAME/build/XCFrameworks/release/ zip -r $MODULE_NAME.xcframework.zip $MODULE_NAME.xcframework popd # fetch tags git fetch --tags # get previous release tag PREVIOUS_RELEASE_TAG=$(git tag --sort=-creatordate | grep -v ^version | head -n 1) echo "previous release tag: $PREVIOUS_RELEASE_TAG" # create github draft release echo "creating github release $RELEASE_VERSION" gh release create $RELEASE_VERSION -d --generate-notes --notes-start-tag $PREVIOUS_RELEASE_TAG gh release upload $RELEASE_VERSION $ZIPFILE echo "retrieving asset api url" # get asset api url of uploaded zip file from github release # eg: "https://api.github.com/repos/{username}/{repo}/releases/assets/132406451" ASSET_API_URL=$(gh release view $RELEASE_VERSION --json assets | jq -r '.assets[0].apiUrl') # add suffix .zip to url ASSET_API_URL="${ASSET_API_URL}.zip" # Generate Package.swift ./scripts/generate_SPM_Manifest_File.sh $ZIPFILE $ASSET_API_URL # commit Package.swift and add tag git checkout -b $TMP_BRANCH git add . git commit -m "release $VERSION" git tag -a $TAG -m "$MODULE_NAME $VERSION" git push origin $TAG # update github release to point to the new tag gh release edit $RELEASE_VERSION --tag $TAG 4. Android and iOS Implementation Methods In this project, we introduced a new common module to our existing app using Kotlin Multiplatform (KMP). To minimize potential platform-specific issues, we carefully selected and implemented features that could work reliably across Android and iOS. The focus was on establishing a cross-platform module by selecting OS-independent functionality and keeping implementations simple for initial testing in production environments. Below is an outline of the feature selection criteria and the implementation process. 4.1 Feature Selection To identify potential challenges of deploying KMP in production, we prioritized features that would not depend on platform-specific implementations and could be handled with minimal dependencies. The criteria for feature selection included: OS-Independent Functionality : We select the feature that would be OS-independent to avoid unexpected issues in production, leaving out elements that required specific OS-level controls, such as communication, storage, and permissions. Minimizing Additional Libraries : To reduce the risk of maintenance, we select the feature that could be implemented only with the Kotlin standard library without relying on additional libraries. Library Prioritization : When selecting libraries, we prioritized official Kotlin libraries first, then libraries recommended in official Kotlin documentation, and finally, third-party libraries as a last selection. Based on these criteria, input validation were chosen as the initial cross-platform functionality to implement with KMP. And full-width/half-width character transformation feature added. Android Input Validation Implementation By default, Android implementation has only lack of library functionality or interface difference problems, but it was no-big deal. The input validation feature was structured according to general object-oriented programming (OOP) principles, with an emphasis on reusability and consistency. 1. Defining Common Interfaces : We defined Validator and ValidationResult interfaces to create a consistent foundation for validating input across both platforms. abstract class ValidationResult( /** * Informations about input and fail reason. */ val arguments: Map<String, Any?>, requiredKeys: Set<String> ) fun interface Validator<T, R : ValidationResult> { /** * @return validation result or `null` if the target is valid. */ operator fun invoke(target: T): R? } 2. Validator Implementation by Input Type : Separate validators and result classes were created for different input types, such as email and password validation. class IntRangeValidator( /** * min bound(inclusive). */ val min: Int, /** * Max bound(inclusive). */ val max: Int ) : Validator<String, IntRangeValidationResult> { companion object { const val PATTERN = "0|(-?[1-9][0-9]*)" val REGEX = PATTERN.toRegex() const val ARG_NUMBER = "number" const val ARG_RANGE = "range" const val ARG_PATTERN = "pattern" } val range = min..max override fun invoke(target: String): IntRangeValidationResult? { when { target.isEmpty() -> return RequiredIntRangeValidationResult() !target.matches(REGEX) -> return IllegalPatternIntRangeValidationResult(target, PATTERN) } return try { target.toInt(10).let { number -> if (number !in range) { OutOfRangeIntRangeValidationResult(target, range) } else { null } } } catch (e: NumberFormatException) { OutOfRangeIntRangeValidationResult(target, range) } } } 3. Test Code Creation : To validate the module’s accuracy across platforms, we implemented extensive test cases using the kotlin-test package, ensuring stable functionality on both Android and iOS. import kotlin.random.Random import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull class IntRangeValidatorTest { private var min = 0 private var max = 0 private lateinit var validator: IntRangeValidator @BeforeTest fun setUp() { min = Random.nextInt() max = Random.nextInt(min + 1, Int.MAX_VALUE) validator = IntRangeValidator(min, max) } @AfterTest fun tearDown() { min = 0 max = 0 } @Test fun `invoke - decimal number string`() { val validator = IntRangeValidator(Int.MIN_VALUE, Int.MAX_VALUE) for (number in listOf( "0", "1", "111", "${Int.MAX_VALUE}", "-1", "${Int.MIN_VALUE}", "${Random.nextInt(Int.MAX_VALUE)}", "-${Random.nextInt(Int.MAX_VALUE - 1)}" )) { // WHEN val result = validator(number) // THEN assertNull(result) } } } Full-width/Half-width Character Transformation Implementation In addition to input validation, we implemented a character transformation feature to automatically convert between full-width and half-width characters based on application requirements. 1. Defining Extendable Interface : To enable multiple and complex conversions, we defined an interface that could be inherited to handle various character transformations. Kotlin has functional interface( fun interface ) and operator function( operator fun ) features helped to implement this. fun interface TextConverter { operator fun invoke(input: String): String operator fun plus(other: TextConverter) = TextConverter { input -> other(this(input)) } } 2. Defining Mapping Constants for Conversion : We created a character mapping table that listed the full-width/half-width characters and their conversions, allowing transformations by referencing predefined mappings. open class SimpleTextConverter( val map: Map<String, String> ) : TextConverter { override operator fun invoke(input: String): String { var result = input for ((key, value) in map) { result = result.replace(key, value) } return result } } class RemoveLineSeparator( map: Map<String, String> = mapOf( "\n" to "", "\r" to "" ) ) : SimpleTextConverter(map) object HalfwidthDigitToFullwidthDigitConverter : SimpleTextConverter( mapOf( "0" to "0", "1" to "1", "2" to "2", "3" to "3", "4" to "4", "5" to "5", "6" to "6", "7" to "7", "8" to "8", "9" to "9" ) ) val NUMBER_CONVERTER = FullwidthDigitToHalfwidthDigitConverter + RemoveLineSeparator() 3. Automatic Conversion Functionality : The transformation function was designed to automatically convert full-width characters to half-width or vice versa, creating a consistent and predictable input experience. By selecting these OS-independent features and implementing them with KMP, we were able to establish a stable, reusable module that could be deployed reliably across Android and iOS. Integration into iOS Our KMP code was distributed as a Swift Package, our iOS team using XcodeGen to manage Xcode project files. Integrating KMP code into iOS app can be easily done by add 4 lines code to project.yml file. packages: + Umbrella: + url: https://github.com/your-org/your-android-repository + minorVersion: 1.0.0 targets: App: dependencies: + - package: Umbrella - package: ... However, since our code resides in private repositories, some additional setup is required. For full details see: Credential Setup for Private Repositories in SwiftPM 5. Issues in KMP Cross-Platform Module Implementation During the development of a KMP common module, several technical challenges arose, particularly with handling basic functionalities, multibyte characters, encoding. Below is an overview of these issues and how they were resolved. No Unicode Codepoints Support in Kotlin Standard Library In order to accurately process multibyte characters such as Kanji and surrogate pairs within input validation, we decided to implement Unicode Codepoint-based regular expressions. This approach allowed us to precisely match and validate characters based on their positions within the Unicode spectrum rather than merely treating them as individual characters. However, we encountered issue. Kotlin’s String class does not natively support handling Unicode Codepoints, nor does it provide an official library for this purpose, especially surrogate pairs. So to ensure precise handling of multibyte characters based on codepoints, we use a third-party library, which allowed us to match complex characters like Kanji more accurately within our regular expressions. No Encoding Support for Non-UTF To maintain compatibility with legacy systems, it was necessary to support encoding in Shift-JIS (MS932). But KMP does not support Shift-JIS encoding natively. Text transfer to the legacy system required to check encodable or not in MS932, for which we opted to use the ktor-client library to handle encoding. However, the iOS version of ktor-client only supports UTF-based encoding schemes, making it challenging to implement MS932 encoding. Due to MS932 encoding limitations, we abandoned the use of code points for Kanji verification. Instead, we declared a constant that included the entire list of Kanji characters required for validation, converting these to Unicode codepoints for reference when needed. Unicode Codepoint Issue When implementing full-width and half-width character transformations, we encountered discrepancies in codepoint differences between certain characters, making a simple addition/subtraction approach ineffective. For example, the Japanese full-width characters ァ' ( U+30A1 ) and ア ( U+30A2 ) differ by only 1 in codepoints. In contrast, the half-width characters ァ ( U+FF67 ) and ア ( U+FF71 ) differ by 10 in codepoints. This inconsistency meant that a unified approach to transformation was not feasible. We resolved this by creating a constant mapping table for all transformations, explicitly defining all full-width and half-width characters and their respective mappings. This approach allowed us to handle a variety of characters accurately in transformation operations. By addressing these challenges, we enhanced the stability and completeness of our KMP common module, ensuring accurate functionality across both Android and iOS platforms. 6. Effects Technical Effects: Process Consistency : The implementation of KMP has minimized operational discrepancies between iOS and Android, reducing the frequency of errors during QA. Code Reusability : Code validated by the Android team is also used on iOS, enhancing development efficiency across both platforms. OS Collaboration and Optimization of Development Resources: Reduced Communication Burden : KMP allows the Android team to handle most maintenance independently, enabling the iOS team to focus on version upgrades and minor maintenance. This leads to more efficient use of development resources and strengthened collaboration between the teams. Project Management Challenges: Development and Maintenance Costs : Initial setup requires time, but afterward, development can continue as usual. However, development costs may increase due to restrictions on using Android-specific and Java-based libraries. Resource Allocation : Development processes focused on the Android team can lead to resource shortages during busy periods. As the Android team primarily manages features implemented with KMP, the iOS team's understanding is relatively low, necessitating balanced resource distribution and training. 7. Moving Forward: Future Plans and Challenges Implementing Future Expansion Plans Through Ongoing Education and Training Currently, our team is developing and executing an internal education and training program to make more effective use of Kotlin Multiplatform (KMP) technology. This program goes beyond technical details, focusing on enhancing teamwork and project management skills. By doing so, we aim to not only improve technical abilities but also manage projects more effectively and strengthen collaboration between teams. Future Plans: Transitioning Common Logic to KMP Going forward, our team plans to transition more common logic to KMP, which will help maximize code reuse between iOS and Android applications and reduce the complexity of maintenance, thereby enhancing development efficiency. Key Logic to Transition: API Client: BFF, OCR Business Logic: Cache management, etc. Utilities: Formatting of text (time, usage fees), version comparison (terms of use), etc. Local Storage: App settings, authentication tokens, etc. By implementing these plans, we expect to strengthen the efficiency and collaboration of cross-platform development, enabling our team to perform development tasks across platforms more effectively. Thank you for reading, and we hope this provides a useful reference for teams that have not yet applied KMP technology.
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の12日目の記事です🎅🎄 学びの道の駅の中西です。今年は学びの道の駅プロジェクトが立ち上がり組織化されました。そんな中、社内Podcastの運営も行っており、今年のアドベントカレンダーではその内容もお届けしたいと思います。 「学びの道の駅」とは? 「学びの道の駅」は、社内で頻繁に開催される勉強会をもっと便利に、そして効果的に活用するために立ち上げられたプロジェクトです。社内の有志が中心となり勉強会の開催を支援し社内の知識共有を促進することを目的としています。 合同勉強会 第一回目の「学びの道の駅ポッドキャスト」では、社内で行われた合同勉強会について、運営メンバーである朝日さん、きーゆのさん、リナさんにインタビューを行いました。このポッドキャストでは、勉強会の背景や目的、運営の工夫、そして今後の展望について詳しくお聞きしました。 インタビュー HOKAさん(インタビュアー): まずは「道の駅プロジェクト」がどのように始まったのか教えてください。 HOKAさん: このプロジェクトは社内で学びに対して前向きな文化をさらに広めるために始まりました。景山さんをはじめとする数名が、社内の勉強会を支援する仕組みを作ろうと集まったのがきっかけです。 HOKAさん: 合同勉強会の背景と目的について詳しく教えてください。 朝日さん: 新入社員向けのオリエンテーションで気づいたのですが、他のシステムと関連する情報を知る機会が少ないと感じました。それで、最新の情報をキャッチアップできる場が必要だと思い、合同勉強会を始めることになりました。 HOKAさん: 勉強会の運営で工夫されている点は何ですか? リナさん: 他のシステムやプロダクトの最新情報を共有できるようにして、より広範な知識を提供することを目指しています。また、参加者を増やすために事前の準備をしっかり行い、運営メンバー自身が積極的に関与しています。 HOKAさん: 初回の勉強会の成果と反響はいかがでしたか? きーゆのさん: 事前の見込みでは34人程度の参加でしたが、実際にはZoom参加者を含めて約80名が集まりました。普段関わらない人との交流ができたり、他のプロダクトの最新情報を得ることができたというポジティブな反応が多くありました。 HOKAさん: 勉強会の継続と今後の取り組みについて教えてください。 朝日さん: 今後は、新たなテーマ別ディスカッションや職種別の交流の場を作り、より多くの社員が気軽に参加できる環境を整えていきたいと考えています。 リナさん: 次回の登壇者も既に決定しており、継続的な開催を目指しています。社内外の情報発信も強化していきます。 HOKAさん: 「道の駅プロジェクト」を社外に発信する狙いは何ですか? リナさん: 社内外の興味を引くことで、より多くの参加者を募ることです。社外への発信を通じて、社内の学びの文化を広めたいと思っています。 HOKAさん: 最後に、今回のインタビューを振り返って感じることはありますか? 朝日さん: 参加者としての視点と運営側としての視点の違いを実感しました。運営側に立つことで、イベントの重要性や意義がわかり、盛り上げたいという気持ちが強くなりました。 きーゆのさん: 今までズームで見るだけだったのですが、運営側に立つことでコメントやリアクションが力になると感じました。今後も積極的に参加していきたいです。 まとめ 「道の駅プロジェクト」が目指すのは、単なる勉強会の場の提供にとどまらず、社員同士の交流と知識共有を通じて、社内の文化を一層豊かにすることです。継続的な勉強会の開催と広報活動を通じて、より多くの社員が参加し、学び合う場を作り上げていくことで、社内全体のスキルアップと連帯感の醸成に貢献していくことでしょう。 今回は合同勉強会の詳細と、その運営の背景、今後の展望についてお届けしました。次回の勉強会も楽しみにしてください!
アバター
この記事は KINTOテクノロジーズアドベントカレンダー2024 の11日目の記事です🎅🎄 メリークリスマス✌️🎅 KINTOテクノロジーズで my route(iOS) を開発しているRyommです。 本記事ではカスタムスタイルの紹介をします。 はじめに 私がこの書き方を知ったのはApp Dev Tutorialsがきっかけです。 https://developer.apple.com/tutorials/app-dev-training/creating-a-card-view#Customize-the-label-style なんてスタイリッシュなんだ…! カスタムスタイルを使うことで、SwiftUIのコードが格段に読みやすく、洗練されたコードになる…!私もスタイリッシュなコードを書きたい! そんな衝動に駆られて使い始めましたが、実際かなり便利で読みやすいのでおすすめです。 特に、用途別に乱立した構造体名を覚えていなくても ~~Style() にドットで候補を探せるところが気に入っています。 カスタムスタイルのつくりかた 例えばLabelのカスタムスタイルを作成する場合、 LabelStyle を継承した構造体を作成し、プロトコルに準拠したメソッド(ここでは makeBody(configuration:) )内にスタイルを定義します。 configurationに含まれる値はものによって異なるので都度調べる必要がありますが、LabelStyleConfigurationに関してはTextとImageのViewが入っています。 /// 文字+アイコン のラベルスタイル struct TrailingIconLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { HStack { configuration.title configuration.icon } } } さらに LabelStyle を拡張して、作成したカスタムスタイルを静的プロパティとして追加すると、呼び出し時に .labelStyle(.trailingIcon) のように呼び出すことができて可読性が向上します。ン〜スタイリッシュ! extension LabelStyle where Self == TrailingIconLabelStyle { static var trailingIcon: Self { Self() } } もし「spaceを指定したい」など、引数を持たせたい場合はカスタムスタイルにメンバプロパティを追加することで実現できます。 /// 文字+アイコン のラベルスタイル struct TrailingIconLabelStyle: LabelStyle { // デフォルト値を設定しておくとドット始まりの呼び出し方法もキープできる var spacing: CGFloat = 4 func makeBody(configuration: Configuration) -> some View { HStack(spacing: spacing) { configuration.title configuration.icon } } } // 呼び出し Label().labelStyle(.trailingIcon) // spaceにはデフォルト値が使われる Label().labelStyle(TrailingIconLabelStyle(spacing: 2)) // spaceを2に指定 使いどころ アプリ全体で広く使う共通デザインや、上記の TrailingIconLabelStyle のように普遍的なカスタムスタイルに使うと良いでしょう。 たとえば、my routeではProgressViewで使っています。 ProgressView自体のスタイル設定もですが、ProgressViewを表示中に背景をグレーっぽくするのもスタイルに含めることができます。 struct CommonProgressViewStyle: ProgressViewStyle { func makeBody(configuration: Configuration) -> some View { ZStack { ProgressView(configuration) .tint(Color(.gray)) .controlSize(.large) Color(.loadingBackground) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } extension ProgressViewStyle where Self == CommonProgressViewStyle { static var common: Self { Self() } } ちなみに、ProgressViewに background() を指定するとProgressViewに必要なサイズのみしか描画されないので、ZStackでColorをProgressViewの下に敷き、背景色が与えられたサイズ全体に広がるようにしています。 このようにスタイルを作成することで、以下のように簡潔でスタイリッシュに書けるようになりました。 struct SomeView: View { @State var loadingStatus: LoadingStatus var body: some View { SomeContentView .overlay { if loadingStatus == .loading { ProgressView() .progressViewStyle(.common) } } .disabled(loadingStatus == .loading) } } おわりに カスタムスタイルの紹介でした! 以下のページにあるものはカスタムスタイルを作成できます。 https://developer.apple.com/documentation/swiftui/view-styles よりスタイリッシュなコードを目指して一歩前進 🦌 🎄
アバター
こんにちは、学びの道の駅チームのHOKAです。 学びの道の駅チームは、部活動のような感じで業務時間でありながら、本業+オンで活動していました。が、この秋(9月?)から技術広報グループにジョインしました。 その詳細についてはこちらのブログを御覧ください↓↓ https://blog.kinto-technologies.com/posts/2024-12-03-the-next-goal/ 技術広報グループにジョインしたこともあり、12月のアドベントカレンダーを学びの道の駅チームからも書こうということになりました。 以前、共同でTech Blogを書いたので、今回も同様に気軽なノリでMTGをセッティングしたら、KINTOテクノロジーズ Tech Blogの発起人である中西さんが「15本、書こう」と息まいております。 「あれ、そんな話だったかな~」と思って、まずは15本のテーマを伺いました。 それがこちら ポッドキャスト 10本 まなびぃ 1本 学びの道の駅ポータル1本 もうすぐ一年 技術広報グループでこんなことやるよ このテーマを聞いて改めて、「書くことある?面白い?」と思ってしまった私。 「いやいや、どんどん書いていこう。例えば、春にBlog書いたじゃん。社内の反響とか、雰囲気が変わったとか、あるじゃん。」と自信のある中西。 もともと広報をやっていたので、なんらか文章にすることは出来ると思って、「はぁ、じゃあ、まぁやってみます。」というテンションで終話しようしたところ、きんちゃんが 「HOKAさん、納得してないんじゃないですか?無理していませんか?」と声をかけてくれました。 仕事なので、納得していないことも無理することもあるのは当たり前だろうと思い、正直に「YES」と答えました。そして、「書くほどのネタがないのに、なぜ書くのか?」ということも尋ねました。私なら、現状の活動内容を特に伝えるべきとも思わないし、読んでも面白いとも思わないのです。(言いたい放題) ここから対話形式でお届けします。 中西「KTCに入る前の自分に語りかけるように書いてほしい。こういう社内の雰囲気だったら入社したいと思うかもしれないじゃん。」 HOKA「うーん。全然、読みたいと思わないな...。」 中西「そもそも、テックブログは、1年に一人か二人にしか刺さらない記事で良いんです。」 HOKA「!?」 中西「正直、TechBlogの中にはどこの会社でも起きている当たり前のことが書かれた記事もあります。でも、TechBlogがなかったら、外部の人からはKTCで今何が起きているかは見えないんです。だから、大発見でなくても良い。会社で起きたことをただ書けば良い。それを読んだ人には、そんなことがあったんだと伝わるから。つまり、やったことを文章に残すだけで良いんです」 HOKA「!?!?!?!?!?!?!?」 中西「自分がやったことをただ書くだけ。それならハードルが低いし、誰でも書ける。そして、その内容がたとえ会社で起きたことの一部分だったとしても、各自がそれをやっていけば、集合したときにKTCってこんな会社だってことが見えるようになる。」 HOKA「めっちゃ理解しました。(頭にパッチワークの図を浮かべながら)」 きん「それが中西さんの戦略ですよね。他社のTechBlogとの差別化ポイントなんです。ちなみに、HOKAさんの悩みはTechBlogを書いていないエンジニアの悩みと同じです。私もHOKAさんの悩みを聞いてスッキリしました。」 ちなみに、私はというと、過去に企業広報を10年やっており、「いかに私的感情を省き、端的に業績やブランドイメージを伝えていくか」ということをゴールに置いて文章を書いて来ました。読み手は時間のない記者さんや編集者だったからです。 エンジニアが中心の会社で、エンジニアのコミュニケーションに触れ、学ぶことができたと感じる一日でした。入社してから一番と言っても良いくらい衝撃だったので、早速Blogにしたためました。 まとめ TechBlogは起きたことをただ書けば良い。 学びの道の駅は、正直な気持ちを話せる、素晴らしいチームです。 そして、メンバーの「分からない」に寄り添ってくれる素晴らしい仲間です。 参加している人も学んでいます。
アバター
This article is part of day 11 of KINTO Technologies Advent Calendar 2024 Merry Christmas! ✌ I'm Ryomm, and I work on developing the My Route iOS app at KINTO Technologies. In this article, I will introduce custom styles. Introduction App Dev Tutorials were the reason I learned to create custom styles. https://developer.apple.com/tutorials/app-dev-training/creating-a-card-view#Customize-the-label-style How stylish...! Using custom styles significantly enhances the readability and sophistication of SwiftUI code...! "I want to write stylish code, too!" Initially, that was what inspired me to start using it, but now I recommend it because it’s genuinely convenient and makes the code much easier to read. What I particularly like is that you can search for options using dots in ~~Style() even if you don’t remember the specific structure names, as they are organized based on their purpose. How to create a custom style For example, if you want to create a custom style for a Label, create a structure that inherits LabelStyle and define the style in a protocol-compliant method (in this case makeBody(configuration:) ). The values within the configuration object vary depending on what you're creating, so it's important to check each time. For LabelStyleConfiguration, it includes Text and Image views. /// Character + Icon LabelStyle struct TrailingIconLabelStyle: LabelStyle { func makeBody(configuration: Configuration) -> some View { HStack { configuration.title configuration.icon } } } You can also extend LabelStyle to add your custom style as a static property, which can be called as .labelStyle(.trailingIcon) when invoked, and improve readability. So~ stylish! extension LabelStyle where Self == TrailingIconLabelStyle { static var trailingIcon: Self { Self() } } If you want to have a parameter, such as "specify a space," you can do this by adding a member property to your custom style. /// Character + Icon LabelStyle struct TrailingIconLabelStyle: LabelStyle { // you can set the default value to preserve the way the dot starts are called var spacing: CGFloat = 4 func makeBody(configuration: Configuration) -> some View { HStack(spacing: spacing) { configuration.title configuration.icon } } } // call The default value is used in Label().labelStyle(.trailingIcon) // space Label().labelStyle(TrailingIconLabelStyle(spacing: 2)) // Set space to 2 Uses You can use it for common designs that you use widely throughout apps, or for universal custom styles like TrailingIconLabelStyle above. For example, my route uses it in ProgressView. While ProgressView itself is styled, you can also include a grayish background when ProgressView is displayed. struct CommonProgressViewStyle: ProgressViewStyle { func makeBody(configuration: Configuration) -> some View { ZStack { ProgressView(configuration) .tint(Color(.gray)) .controlSize(.large) Color(.loadingBackground) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } extension ProgressViewStyle where Self == CommonProgressViewStyle { static var common: Self { Self() } } By the way, when you use background() with a ProgressView, it only applies to the area required by the ProgressView. To ensure the background color covers a larger area, you can use a ZStack to place the color beneath the ProgressView, allowing the background to expand to the desired size. By defining a style in this way, you can achieve concise and elegant code, as shown in the example below. struct SomeView: View { @State var loadingStatus: LoadingStatus var body: some View { SomeContentView .overlay { if loadingStatus == .loading { ProgressView() .progressViewStyle(.common) } } .disabled(loadingStatus == .loading) } } Conclusion That wraps up this introduction to custom styles! You can create custom styles on the following page. https://developer.apple.com/documentation/swiftui/view-styles Take a step toward writing more stylish and elegant code!
アバター