TECH PLAY

KINTOテクノロジーズ

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

936

はじめに こんにちは!KINTOテクノロジーズでiOSアプリケーションを開発しているFelixです。Swiftに焦点を当てたカンファレンスに行くのは初めてでした。2024年3月22日から24日まで、渋谷で開催されたtry! Swift 2024 Tokyoに参加しました。業界の最新トレンドに触れ、他のエンジニアとのネットワークを広げる絶好の機会となりました。 プレゼン いろいろな説得力のあるプレゼンの中で、特に印象に残ったものを2つ挙げさせてください。 まず、DuolingoのAIチューター機能についてのプレゼンです。講演者のXingyu Wangさんは、AIチューター機能の実装に関して講演されました。また、チャットインターフェイスの構築や、有益なフレーズのレイテンシーの最適化といった課題に触れ、GPT-4の機能を活用した解決策を紹介しました。フロントエンドだけでなく、現在直面している課題にも言及しながら、ソフトウェア全体のアーキテクチャーについてお話しいただけて非常に良かったです。個人的な話ですが、以前私は「日本人ユーザー向けの英語学習アプリを開発する」という、同じような目標を持っていました。この知識は、同じようなサービスを作る上で非常に有用です。よくできたロールプレイ機能を組み込むことで、学習者が実生活に近い環境で会話スキルを練習することができるようになります。 もう一つご紹介したいのは、フレームワークのコミュニティで有名なPoint-Freeによるものです。Swiftのversion 5.9で導入されたSwiftマクロテストに関する発表が特に印象的でした。コンパイラプラグインであるマクロは、新しいコードや診断、修正を生成することで、Swiftのコードを強化します。プレゼンターのお二人は、Swiftの微細なニュアンスを強調し、これらのマクロを作成することやテストすることの複雑さを紹介くださいました。また、彼らのテストライブラリであるswift-macro-testingが、マクロのテストプロセスをより簡素化し、効率的かつ効果的にすることで、Appleのツールを向上させる方法についても示してくださいました。プレゼンターの方々がSwiftを深く理解した上で開発ワークフローの改善に向けて革新的なアプローチを取っているかがわかりました。 ブース ブースエリアは、企業と交流したり、ノベルティを集める参加者でにぎわっていました。サイバーエージェントのブースは特に魅力的で、参加者がポストイットにコードの要約を書き込めるホワイトボードが設置されていました。このインタラクティブなブース企画は、知識を深めるのに役立ったのと同時に、Swiftへの関心をさらに高めるのに効果的だと思いました。 今回のカンファレンスでは、通常の質疑応答ではなく、プレゼンのあとに質問がある人は指定されたブースで登壇者と直接会って話すことができる、という新しいスタイルが採られていました。これより、参加者がより質問しやすくなり、登壇者との交流ができるため、より良いコミュニケーションやネットワーキングの機会になったと思います。 ワークショップ カンファレンス最終日には、好きなワークショップを選んで参加することができました。私はTCAに関するワークショップを選び、約200人を収容する大きな部屋の後ろの方に座りました。このワークショップでは、主にコンポーザブル・アーキテクチャーを使用してサンプルの「SyncUp」アプリを開発する方法について解説されていました。私も最初は一緒にコーディングしようとしましたが、最終的には見学することにしました。興味深い点は、このフレームワークが副作用を管理するための構造化されたアプローチを提供していることです。アプリの外部と相互作用する部分がテスト可能で、理解しやすいものとなっています。ユニットテストのプロセスは特に効率的で明確に見えました。 さいごに 今回初めてtry! Swift Tokyoに参加して、非常に充実した良い経験となりました。このカンファレンスは業界のリーダーや仲間とつながるためのプラットフォームとなっており、私は最先端のSwift技術に夢中になりました。プレゼンは有意義で、iOS開発における現実世界の課題と創造的なソリューションについて深く掘り下げた内容が提供されていた印象でした。インタラクティブなブース企画や専門分野に特化したワークショップは、非常に良い学習やネットワーキングの機会となり、このカンファレンスの大きな価値となっていました。最後まで読んでくださり、ありがとうございました!この記事を読んでご興味を持たれた方はぜひ来年のtry! Swiftにぜひご参加ください!
アバター
Introduction Hello, Tech Blog readers. We have recently decided to implement Marketing Cloud and to use the " Norikae GO email delivery" in it, considering the creation of a Journey to trigger an automated process instead of sending individual emails. A Journey is a feature that automatically deploys multiple marketing strategies when a customer takes a specific action. For example, when a customer clicks on a specific link in an email, the relevant information is automatically delivered as part of an automated marketing process. Unfortunately, we were having troubles finding a way to add Journey Builder as an activity in Automation Studio. So, I have summarized in this article the results of the various trials we did. Email Delivery Partner There are several reasons for using Journey Builder: ・Can leverage branching, randomness, and engagement ・Can be integrated with Salesforce, for example, when creating tasks and cases, updating objects, etc. However, Journey Builder does not allow you to execute scripts or SQL queries. For example, you need to merge synced data sources before sending a large volume of emails. In such cases, Journey Builder must be called after these activities are completed in Automation Studio. Therefore, it is desirable to integrate Automation Studio with Journey Builder to send emails. Let's see how to set this up together. Settings Create an Automation, and add the Schedule as the starting source. Configure the Schedule to the future time and save it. Remember to save it, otherwise later settings will not work. Add your desired activity, such as SQL queries and filters. This is essential for integrating with Journey. Journey cannot be triggered if no data extension is selected. Create a Journey. Add a data extension as the entry source. Select the data extension used in Step 2. This is important. If you choose a different data extension, you will not be able to integrate with Automation in Step 1. Note: At this point, even if you save the journey and return to Automation, you will not be able to select the journey from the activities. This is because there is no "Journey" option for Automation activities. But, wait a moment. Now, I'm going to show you some magic! ![Step3-2](/assets/blog/authors/Robb/20240319/03-2.png =300x) In Journey, click "Schedule" at the bottom of the canvas, select "Automation" as the schedule type, and then click "Select." Can't choose "Automation" because it is inactive? Why don't you go back to Step 1 and save Automation? In "Schedule Summary," click "Schedule Settings" and select the Automation you created in Step 1. Edit a contact's rating to specify the records to be processed by Journey. Add email, and flow control, etc. Your setup is now complete! Let’s validate and activate the journey. Don't worry, emails will not be sent immediately after activation, as the timing of the transmission depends on Automation. Back to the Automation, now the Journey was added to Automation on its own, right? Don't you think it's amazing? Finally, summon the courage to activate your Automation. See, every time Automation is triggered, Journey will also be triggered! Thank you for reading. Here, I am going to take a break with a cup of coffee. I hope you will all refresh yourselves with your favorite drink and enjoy the automatic email delivery. Happy marketing! Source: https://www.softwebsolutions.com/resources/salesforce-integration-with-marketing-automation.html
アバター
Introduction Hello everyone! I am Kin-chan from the KINTO Technologies' Development Support Division. I usually work as a corporate engineer, maintaining and managing IT systems used throughout the company. The other day, I presented the "Study session in the format of case presentations + roundtable discussions, specialized in the corporate IT domain" at the event " KINTO Technologies MeetUp!" 4 case studies for information systems shared by information systems - " In this article, I will introduce the content of the case study presented at that study session, along with supplementary information. The Presentation You can check below for the full presentation material (in Japanese): [An Introduction to AGILE SaaS] The Secrets to Achieving Maximum Results Quickly with Minimum Workload In addition to the slides I used in my presentation, I will provide additional information to clarify any difficult parts and cover topics I couldn't address at the event. Title Selection First of all, I'd like you to examine the title. Many people interpret "Agile" in different ways, making it daunting to include in the title of a presentation. However, I chose to have it anyway because I hope that someone who listened to or saw my presentation might gain insights like "Oh, so this can also be considered Agile" or "It's not such a difficult topic," and inspire them to take new actions. (Of course, the fact that it's an "attractive" keyword was also a factor in my decision.) What I Will vs. Will Not Speak About Today Since I used the keyword "Agile" in the title, I thought it would be good to focus on content that can be linked to the value of Agile software development. If you're interested in hearing more detailed information about processes or the small stories that occurred during projects, please consider joining KINTO Technologies. Background The introduction of IT Service Management (ITSM) tools, which include inquiry and request management, began with the IT team. Due to its relatively smooth implementation, there was a basis for extending it to management departments beyond IT. Before this flow was established, there wasn’t many opportunities to interact with "other managing departments beyond IT" within the company. Personally, I had previous project experiences with many non-IT departments, including before my previous job. So, when I was appointed to drive this project, I felt glad because I thought I could leverage my past experiences. The decision to opt for an Agile approach stemmed from the background of having a rough goal in mind but not having concrete set of requirements or functions established, and wanting to achieve success with minimal workload while still creating value. Instead of a rigidly defined phased implementation (as one would do in a Waterfall model), the Agile approach, which involves iterating through dialogue and course corrections while building minimal viable products, seemed more suitable. I have this slide here that says, "I think it's better to go Agile!" It might seem like we had already decided on Agile from the project's inception, but in reality, it was more like, "Hmm, how should we move forward? Let's start by listening to what the stakeholders have to say." It was after conducting hearings with the Administration Department team members that we gained a sense of, "With them, we could proceed with this style!" which led us to adopt the Agile approach mentioned later. About Agile When someone asks me "What is Agile?" within the company, I typically respond with something like, "It's a state where work progresses by focusing on value while iterating Kaizen (continuous improvement) in short cycles." While those familiar with software development may understand the values and principles outlined in the Agile Manifesto, others might not resonate with it. Lately, I've noticed that explaining Agile has become easier with the publication of books like 'The Agile Kata' and other Agile books targeting non-IT audiences. As for the progress of the project... For the next slides, I made a conscious effort to explain "What makes it Agile?" in a way that links back to the values outlined in the Agile Manifesto as much as possible. The message I wanted to convey with this slide is the establishment of mechanisms to minimize unnecessary communication and facilitate immediate engagement in essential conversations. In typical software development scenarios, one common question might be, "What tasks are currently being performed?" and for clarifying "What do we want to accomplish?" . Given that this is a "SaaS implementation with a certain degree of framework already established," I deemed it more appropriate to explore "effective usage based on the existing framework" rather than "defining requirements based solely on current tasks." Furthermore, one of the strengths of low-code tools is their significantly lower cost for the build-break-fix process in the initial stages. This made it feasible to create a prototype providing minimal value before the first meeting. As a result, instead of starting the conversation with "So, what kind of product do you want to create?" during the initial meeting, we were able to begin with discussions focused on specific functional prototypes, asking questions like "How about a system that works like this? Do you notice any issues with it?" This allowed us to engage in discussions centered around tangible, functional examples right from the start. These aspects focus on the following values in the Manifesto for Agile Software Development: Individuals and interactions over processes and tools Working software over comprehensive documentation What I wanted to convey in this slide is “to create value at short intervals, get feedback, and create a mechanism to provide a system that makes sense”. One common aspect in meetings is taking points as homework for internal discussion later. For example, "consider what kind of menu structure is good" or "discuss internally what kind of process flow is best". But this time, rather than leaving such "takeaway considerations" entirely to the other party, we opted to participate in these discussions by being invited as guests to their alignment sessions. By doing so, we can immediately address any questions, concerns, or discrepancies that arise during the conversation, and we can swiftly provide answers or even start making system adjustments on the spot. As a result, despite being in a "separate discussion" setting, we were able to progress not only with specification changes based on the discussions but also with actual functional improvements. Furthermore, I mentioned here that "significant specification changes emerged at this point," but what I meant was that we were able to detect a situation where it was more beneficial to essentially "start over" rather than modify what has been done so far. Of course, this meant discarding what has been built till that point. However, by actively participating in the discussions, we were able to fully understand the necessity and value of rebuilding. This allowed us to make this decision with confidence. These aspects focus on the following values in the Manifesto for Agile Software Development: Customer collaboration over contract negotiation Responding to change over following a plan Finishing the project Through this project, one of the most significant gains I feel I've obtained is "trust." It's just my unilateral opinion, but I feel that I've contributed to creating opportunities where people think, "Working with this person leads to good results," and "I'd like to consult with them again if there's something next time." Certainly, I believe there are many approaches to project management that can yield positive results, not just those aligned with Agile practices like the example we discussed. But If you ever find yourself stuck on how to proceed, I recommend considering the values inherent in Agile as a reference and trying to adjust your actions just a little: Envision your desired outcomes and apply small changes to achieve them. Observe the results of those small changes in behavior and use that feedback to further refine your vision of the desired outcome. Continue to make further small changes in your behavior. Once you're able to repeat this process, it's safe to say you've adopted an 'Agile' mindset. Conclusion As mentioned at the beginning, I hope to inspire anyone who has gone through this material to gain insights such as "Oh, this is also Agile" or "It's not such a difficult topic". I would be happy if this can serve as encouragement for your next actions.
アバター
Introduction Greetings, this is Morino from KINTO Technologies. On June 29th (Thursday) to the 30th (Friday) in 2023, I attended with a colleague the Cyber Security Symposium Dogo 2023 held in Matsuyama City, Ehime Prefecture. The purpose of the event is to recognize the importance of countermeasures against cyberattacks as digitalization accelerates with the development of society that coexists with the coronavirus, and to fight cyberattacks with the power of local security. The purpose of the seminar was to deepen discussions on policy trends, technological trends, and examples of cyber attacks. We were able to get a lot of inspiration and knowledge from the lectures and other participants. When I arrived at Matsuyama Airport, we were greeted by Mican, a mascot promoting the image of Ehime Prefecture. There was also a mikan (mandarin orange) juice tower and a mikan juice faucet. There were many interesting talks and presentations at the symposium, but I would like to introduce some of the ones that left an impression on me. (See a full list of talks and presentations here .) Japan's Cybersecurity Policy First of all, Mr. Tomoo Yamauchi (Director-General, Cybersecurity Office, Ministry of Internal Affairs and Communications) gave a keynote speech on "Japan's Cybersecurity Policy." Under the theme of "leaving no one behind," Mr. Yamauchi explained the country's efforts to secure a free, fair and safe cyberspace. This included changes in targets during Cybersecurity Awareness Month, and improvements in cloud usage within government agencies, etc. I felt that the theme of “leaving no one behind” was wonderful. Security (Security + Community) and Generated AI As for the night session, I listened to a lecture on "Security (Security + Community) and Generated AI" by Mr. Tsuneyoshi Hamamoto (IT Integration Department, Energia Communications, Inc.) and Mr. Matcha Daifuku (Risk Consulting Department, luck Technology, Inc.). Mr. Hamamoto explained the concept of secuminity , a term coined by combining security with community. Secuminity is where people concerned with security interact, share knowledge and experiences, as well as collaborate and learn from each other online and offline. I understood it to be a community that contributes to improving security. Next, he shared his knowledge on Generative AI. The presentation materials are available here (in Japanese). From a security perspective, while we had expectations for its use in detecting suspicious activity from logs, we were also concerned about its use in generating sophisticated phishing emails. Student Research Award Winning Research Presentation Finally, on the second day, outstanding students presented their research findings at the Student Research Award Presentation. I voted for the presentation titled "Proposal of KP-less Method for Individual Cyber Exercises Based on Tabletop Role-Playing Games (TRPG)" as it was the one I found most compelling in the symposium, whereas participants voted for the best presentation. This presentation was made by Ms. Erika Fujimoto (Graduate School of Regional Design and Development, University of Nagasaki), who proposed an exercise method for individuals based on TRPG (Tabletop Role-Playing Games) in “KP-less” style as a cyber exercise scenario. “KP-less” means that there is no one in the TRPG to take on the role of the Game Master, the organizer. I was drawn to it due to my ongoing interest in information security education as a security officer. When I was in elementary school and junior high school, game books became very popular. So I understood that it was an exercise incorporating that method. Summary These were some of the talks and presentations at the Cybersecurity Symposium Dogo 2023. There were many other useful lectures and presentations. The symposium was a valuable opportunity not only to learn about the latest insights on cybersecurity, but also to interact with people who are interested in the same field. I want to thank the organizers, sponsors, and attendees.
アバター
Introduction Hello! Thank you for reading! My name is Nakamoto and I develop the front end of KINTO FACTORY ('FACTORY' in this article), a service that allows you to upgrade your current car. In this article, I would like to introduce a method of how to detect errors that occur in clients such as browsers using AWS CloudWatch RUM. Getting Started The reason why we introduced it was due to an enquiry we received by our Customer Center (CC), where a user tried to order products from the FACTORY website, only to encounter an error where the screen did not transition. This prompted an investigation request. I immediately parsed the API log and checked if there were any errors, but I could not find anything that would lead to an error. Next, I checked what kind of model and browser was being used to access the front end. When examining the access logs from Cloud Front, I looked into the access of the relevant user and checked the User-Agent where I could see: Android 10; Chrome/80.0.3987.149 It was accessed from a relatively old Android device. With that in mind, while analyzing the source of the page where the problem occurred, a front end development team member advised that replaceAll in JavaScript might be the culprit... This function requires compatibility with Chrome version 85 or higher... (Since FACTORY recommends using the latest version of each browser, we hadn't tested cases with old versions such as this case in QA.) *Other members of the team also told me that you can easily search for functions here to see which browsers and versions are supported! Until now, monitoring in FACTORY has detected errors in the BFF layer and notified PagerDuty and Slack, but it has not been possible to detect errors in the client-side, so it was the first time we noticed them through communication from customers. If we continued as-is, we would not be able to notice such errors on the client side unless we received customer feedback, so we decided to take countermeasures. Detection Method Originally, FACTORY's frontend had been loading client.js from AWS's CloudWatch RUM (Real-time User Monitoring). However, this function was not being used for anything in particular (user journeys, etc. are analyzed separately with Google Analytics), so it was a bit of a waste. As I investigated, I learned that RUM allows JavaScript to send events to CloudWatch on a client such as a browser. So using this mechanism, I decided to create a system to send and detect custom events when some kind of error occurs. Notification Method The general flow of notifications are as follows: When an error is detected in the browser, CloudWatch RUM sends a custom event with the error description in the message window.crm("recordEvent", { type: "error_handle_event", data: { /* Information required for analysis. The contents of the exception error */ }, }); Cloud Watch Alerm detects the above events and sends the error details via SNS when the event occurs The above SNS notifies SQS, Lambda picks up the message and notifies the error to OpenSearch (this mechanism uses the existing API error detection and notification mechanism) After Implementation After implementing this mechanism in the production environment and operating it for several months, I can luckily say that critical issues, such as the JavaScript error that resulted in its introduction, have not occurred. However, I have been able to detect cases where errors occur due to unintended access from search engine crawlers and bots, and I have become aware of accesses that I did not pay particular attention to until I introduced it, so it became a reminder of the importance of monitoring and being vigilant. Conclusion In order to enable the best online purchase experiences on websites such as FACTORY, it's very important to prevent as many errors as possible (such as problems when buying items, viewing pages, etc.). However, there is unfortunately a limit as to how much we can guarantee that it works on all customers' devices and browsers. That is why, if an error occurs, it is necessary to show easy to understand messages for the customers (with what they should do next), and a mechanism in place for us, the developers on the operation side, so that we can quickly identify the occurrence and details of the problem. I would like to continue using different tools and mechanisms to ensure stable website operation.
アバター
はじめに こんにちは!KTCグローバル開発部に所属している崔です。 現在 KINTO FACTORY の開発に参加しており、今年はチームメンバーと一緒にWebサービス内のメモリリークの原因を調査し、特定した問題点を修正して解決しました。 このブログでは、調査アプローチ、使用したツール、調査結果、そしてメモリリークに対処するための措置について詳しく説明します。 背景 私たちが現在開発・運用しているKINTO FACTORYサイトには、AWSのECS上で動作しているWebサービスがあります。 このサービスでは、当社が開発・運営している認証サービスである会員PF(Platform)と決済サービスである決済PF(Platform)を利用しています。 今年1月に、このWebサービスでECSタスクのCPU使用率が異常に高まり、一時的にサービスにアクセスできない事態が発生しました。 この際、KINTO FACTORYサイトで特定の画面遷移や操作を行うと404エラーやエラーダイアログが表示されるインシデントが発生しました。 昨年7月にも類似のメモリリークが発生しており、Full GC(Old領域のクリア)が頻繁に発生し、それに伴うCPU使用率の増加が原因であることが判明しました。 これらの事象が発生した場合、一時対策としてECSタスクの再起動で解決できますが、メモリリークの根本原因を究明し、解決する必要があります。 本記事では、これらの事例を踏まえ、現象の調査・分析とそれに基づいた解決策を記載しています。 調査内容と結果の要約 調査内容 最初に、本件で発生した事象の詳細を分析すると、WebサービスのCPU使用率が異常に高まるのは、Full GC(Old領域のクリア)が頻繁に発生することで起きた問題あることが分かりました。 通常、Full GCが一度行われると、多くのメモリが解放され、しばらくの間は再度発生することはありません。 にもかかわらず、Full GCが頻繁に発生するのは、使用中のメモリが過剰に消費されている可能性が高く、これはメモリリークが発生していることを示唆しています。 この仮説を検証するために、メモリリークが発生した期間中に多く呼ばれたAPIを中心に長時間APIを呼び出し続け、 メモリリークを再現させました。その後、メモリの状況やダンプを分析して原因を探ります。 調査に使用したツールは以下の通りです: JMeter でのAPIのトラフィックシミュレーション VisualVM と Grafana を用いたメモリ状態の監視(ローカル環境および検証環境) OpenSearch で頻繁に呼び出されるAPIのフィルタリング また、本文によく現れているメモリの「Old領域」について以下の通りに簡単に説明します: Javaのメモリ管理では、ヒープ領域がYoung領域とOld領域に分かれています。 Young領域には新しく作成されたオブジェクトが格納され、ここで一定期間存続したオブジェクトはSurvivor領域を経てOld領域に移動します。 Old領域には長期間存続するオブジェクトが格納され、ここがいっぱいになるとFull GCが発生します。 Survivor領域はYoung領域内の一部で、オブジェクトがどれだけ長く生存しているかを追跡します。 調査結果 外部サービスのリクエスト時に接続インスタンスが大量に新規作成されており、メモリが無駄に占有されていることによるメモリリークが発生していました。 調査内容の詳細 1. 呼び出し回数が多かったAPIの洗い出し 最初に、多く呼ばれている処理とメモリ使用状況を知るため、OpenSearchでAPI呼び出しサマリのダッシュボードを作成しました。 2. 洗い出ししたAPIをローカル環境で30分間呼び出し続け、結果を分析 調査方法 メモリリークをローカル環境で再現させ、ダンプを取り原因分析を行うため、以下の設定でJMeterを使用してAPIを30分間呼び出し続けました。 JMeterの設定 スレッド数:100 Ramp-up期間(※):300秒 テスト環境 Mac OS Javaバージョン:openjdk 17.0.7 2023-04-18 LTS Java設定:-Xms1024m -Xmx3072m ※Ramp-up期間とは:設定したスレッド数を何秒以内に起動・実行するかの指定される秒数です。 結果と仮説 メモリリークは起きませんでした。実際の環境と異なるためメモリリークが再現しなかったと考えました。実際の環境はDockerで動作しているため、アプリケーションをDockerコンテナに入れて再度検証することにしました。 3. Docker環境で再度APIを呼び出し続け、結果を分析 調査方法 メモリリークをローカル環境で再現させるため、以下の設定でJMeterを使用してAPIを1時間呼び出し続けました。 JMeterの設定 スレッド数:100 Ramp-up期間:300秒 テスト環境 ローカルDockerコンテナ(Mac上) メモリ制限:4 GB CPU制限:4コア 結果 ローカル環境で環境を変えてもメモリリークは起きませんでした。 仮説 実際の環境と異なる 外部APIを呼び出していない 長時間にわたるAPI呼び出しで少しずつメモリが蓄積される可能性がある 大きすぎるオブジェクトがSurvivorに入らず、Old領域に入ってしまう可能性がある やはりローカル環境では再現できないため、本番環境に近い検証環境で再度検証することにしました。 4. 検証環境で外部API関連を長時間叩き続け、結果を分析 調査方法 メモリリークを検証環境で再現させるため、以下の設定でJMeterを使用してAPIを呼び出し続けました。 呼び出し対象API:それぞれ計7本 継続期間:5時間 ユーザー数:2 ループ:200(1000を予定していたが、実際のOrderは少ないため200に変更) Factory API合計呼び出し回数:4000 影響がある外部PF:会員PF(1600回)、決済PF(200回) 結果 Full GCが発生せず、メモリリーク現象は再現しませんでした。 仮説 ループ回数が少なく、メモリ使用量が増加しているが上限に達していないためFull GCが発動されなかった。呼び出し数を増やし、メモリ上限を下げてFull GCを発生させるようにします。 5. メモリ上限を下げ、APIを長時間叩き続ける 調査方法 検証環境でメモリ上限を下げて、JMeterで会員PF関連APIを4時間呼び出し続けました。 時間:4時間 API:前回と同じ7つのAPI 頻度:12ループ/分(5秒/ループ) 会員PF呼び出し頻度:84回/分 4時間の会員PF呼び出し回数:20164回 ダンプ取得設定: export APPLICATION_JAVA_DUMP_OPTIONS='-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/ -XX:OnOutOfMemoryError="stop-java %p;" -XX:OnError="stop-java %p;" -XX:ErrorFile=/var/log/app/hs_err_%p.log -Xlog:gc*=info:file=/var/log/app/gc_%t.log:time,uptime,level,tags:filecount=5,filesize=10m' ECSのメモリ上限設定: export APPLICATION_JAVA_TOOL_OPTIONS='-Xms512m -Xmx512m -XX:MaxMetaspaceSize=256m -XX:MetaspaceSize=256m -Xss1024k -XX:MaxDirectMemorySize=32m -XX:-UseCodeCacheFlushing -XX:InitialCodeCacheSize=128m -XX:ReservedCodeCacheSize=128m --illegal-access=deny' 結果 メモリリークの再現に成功し、ダンプを取得できました。 IntelliJ IDEAでダンプファイルを開くと、メモリの詳細情報を見ることができます。 ダンプファイルを詳しく分析したところ、外部API関連部分でリクエストごとに大量のオブジェクトが新規作成されていること、Util系クラスの一部がSingletonとして扱われていないことが判明しました。 6. Heap Dumpの分析結果 reactor.netty.http.HttpResources 内に HashMap$Node が5,410個作成されており、352,963,672バイト(83.09%)を専有していることが分かりました。 メモリリーク発生箇所特定 reactor.netty.resources.PooledConnectionProvider 内の channelPools(ConcurrentHashMap) でリークが発生しており、格納と取得のロジックに着目しました。 poolFactory(InstrumentedPool) 取得箇所 remote(Supplier<? extends SocketAddress>) と config(HttpClientConfig) から取得した channelHash で holder(PoolKey) を作成 holder(PoolKey) で channelPools から poolFactory(InstrumentedPool) を取得し、同様のキーが存在すれば返し、なければ新規作成 リークの原因は、同一設定でも同一キーと判断されないことです: reactor.netty.resources.PooledConnectionProvider public abstract class PooledConnectionProvider<T extends Connection> implements ConnectionProvider { ... @Override public final Mono<? extends Connection> acquire( TransportConfig config, ConnectionObserver connectionObserver, @Nullable Supplier<? extends SocketAddress> remote, @Nullable AddressResolverGroup<?> resolverGroup) { ... return Mono.create(sink -> { SocketAddress remoteAddress = Objects.requireNonNull(remote.get(), "Remote Address supplier returned null"); PoolKey holder = new PoolKey(remoteAddress, config.channelHash()); PoolFactory<T> poolFactory = poolFactory(remoteAddress); InstrumentedPool<T> pool = MapUtils.computeIfAbsent(channelPools, holder, poolKey -> { if (log.isDebugEnabled()) { log.debug("Creating a new [{}] client pool [{}] for [{}]", name, poolFactory, remoteAddress); } InstrumentedPool<T> newPool = createPool(config, poolFactory, remoteAddress, resolverGroup); ... return newPool; }); channelPoolsは名称の通りChannel情報を保持しているオブジェクトで同様のリクエストが来た際に再利用を行っている。 PoolKeyはホスト名と接続設定のHashCodeを元に作成され、更にそのHashCodeが使用される。 channelHash 取得箇所 reactor.netty.http.client.HttpClientConfig の階層 Object + TransportConfig + ClientTransportConfig + HttpClientConfig PooledConnectionProviderに渡されるLambda式 com.kinto_jp.factory.common.adapter.HttpSupport L5 ここで定義されたLambda式が PooledConnectionProvider に config#doOnChannelInit として引き渡される。 abstract class HttpSupport { ... private fun httpClient(connTimeout: Int, readTimeout: Int) = HttpClient.create() .proxyWithSystemProperties() .doOnChannelInit { _, channel, _ -> channel.config().connectTimeoutMillis = connTimeout } .responseTimeout(Duration.ofMillis(readTimeout.toLong())) ... } 7. channelPools取得時の挙動(図解) キーが一致するケース(正常) channelPools に存在する情報がキーとなり、 InstrumentedPool が再利用される。 キーが不一致のケース(正常) channelPools に存在しない情報がキーとなり、 InstrumentedPool が新規作成される。 今回発生したケース(異常) channelPools に存在する情報がキーとなるが、 InstrumentedPool が再利用されず新規作成されてしまう。 問題箇所の修正と検証 修正箇所 問題となっているLambda式をプロパティ呼び出しに書き換える 修正前 abstract class HttpSupport { ... private fun httpClient(connTimeout: Int, readTimeout: Int) = HttpClient.create() .proxyWithSystemProperties() .doOnChannelInit { _, channel, _ -> channel.config().connectTimeoutMillis = connTimeout } .responseTimeout(Duration.ofMillis(readTimeout.toLong())) ... } 修正後 abstract class HttpSupport { ... private fun httpClient(connTimeout: Int, readTimeout: Int) = HttpClient.create() .proxyWithSystemProperties() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connTimeout) .responseTimeout(Duration.ofMillis(readTimeout.toLong())) ... } 検証 前提条件 MembersHttpSupport#members(memberId: String) を1000回呼び出す。 PooledConnectionProvider#channelPools に格納されているオブジェクトの件数を確認する。 修正前の結果 修正前の状態で実行したところ、 PooledConnectionProvider#channelPools に1000個のオブジェクトが格納されていることが分かりました(リークの原因)。 修正後の結果 修正後の状態で実行したところ、 PooledConnectionProvider#channelPools に1個のオブジェクトが格納されていることが分かりました(リーク解消)。 まとめ 今回の調査では、KINTO FACTORYのWebサービスにおけるメモリリークの原因を特定し、適切な修正を行うことで問題を解決することができました。特に、外部API呼び出し時に大量のオブジェクトが新規作成されていたことがメモリリークの原因であると判明し、Lambda式をプロパティ呼び出しに変更することで解消されました。 このプロジェクトを通じて、以下の重要な教訓を得ることができました: 持続的なモニタリング :ECSサービスのCPU使用率の異常やFull GCの頻繁な発生を通じて、継続的なモニタリングの重要性を認識しました。システムのパフォーマンスを常に監視することで、問題の兆候を早期に察知し、迅速に対処することができます。 早期の問題特定と対策 :Webサービスのメモリリークを疑い、長時間APIを呼び出してメモリ状況を再現させることで、外部サービスのリクエスト時に大量のオブジェクトが新規作成されていることを特定しました。これにより、問題の原因を迅速に特定し、適切な修正を実施できました。 チームワークの重要性 :複雑な問題に対処する際には、チーム全員が協力して取り組むことが成功への鍵となります。今回の修正と検証は、開発チーム全員の協力と努力によって達成されました。特に、調査、分析、修正、検証といった各ステップでの協力が成果を上げました。 調査フェーズでは、多くの苦労がありました。例えば、メモリリークの再現がローカル環境では難しく、実際の環境に近い検証環境で再度検証を行う必要がありました。また、外部APIを長時間にわたって呼び出し続けることで、メモリリークを再現し、その原因を特定するのに多くの時間と労力を要しました。しかし、これらの困難を乗り越えることで、最終的には問題を解決することができ、大きな達成感を得ることができました。 この記事を通じて、システムのパフォーマンス向上と安定性を維持するための実践的なアプローチや教訓を共有しました。同様の問題に直面している開発者の方々の参考になれば幸いです。 以上です〜
アバター
To Be Event Staff at try! Swift Tokyo 2024 With my childcare duties now more manageable, I decided to get more involved in activities and signed up for try! Swift Tokyo 2024! When I noticed they were looking for staff for try! Swift Tokyo 2024, I took the leap and submitted my application. To tell the truth, I had never been to try! Swift Tokyo, even as a participant, so I applied without really knowing what the atmosphere of the venue would be like😅 So in this article, I will share my experiences as a staff member at this event. What is try! Swift Tokyo 2024 try! Swift Tokyo 2024, held in March 2024, is a conference for iOS developers in Japan. Since its inception in 2016, it has consistently served as the largest gathering for professionals in the iOS development community. After a long pause due to COVID-19, this year marked its return for the first time in five years. Please visit the official website for more information. In my experience, iOSDC, another event that is also famous for its large iOS conferences, is largely driven by open speaker requests within Japan to shape the event's schedule. On the other hand, try! Swift Tokyo sources proposals internationally and invites renowned engineers from abroad to enrich its schedule, so there were many situations where we needed to communicate in English. Staff Activities This time, on the day, I worked as a staff member on the organizing side. It was my first time working behind the scenes, but it was a very exciting and enjoyable experience. One week before the event, all the staff gathered for a meeting where responsibilities were assigned. I was assigned to manage the venue, and was asked to do the following: Set up the venue Guiding the participants Venue guidance Handing out lunch boxes Collect garbage Venue teardown Other tasks within the venue ![](/assets/blog/authors/HiroyaHinomori/IMG_2773.jpg =400x) I usually spend most of my time writing code, so I was worried about whether my body could handle three days of physical work. However, I found it surprisingly refreshing to be active and interact with people In particular, I enjoyed talking to the attendees during reception and venue guidance. With many speakers and participants from abroad, try! Swift Tokyo needed English communication, which made me very aware of my limited language skills. Given that it was the first one in five years, there were many newcomers, myself included. Despite the occasional uncertainty about how to do things, everyone was able to work together and enjoy the activities, ending our first day successfully. ![](/assets/blog/authors/HiroyaHinomori/IMG_2784.jpg =400x) On the second day during the venue teardown, it was nice to see that some people had left their signatures on the sponsor boards that remained👍 During the after party which followed the teardown, the participants and staff were able to have fun together, and it was very nice to meet new people there. On the third day, a workshop was conducted for participants, and witnessing their enthusiasm was truly inspiring, leaving me feeling uplifted💪 I had some free time too, so I took the opportunity to exchange information with other staff members. The churrasco I ate at the post-teardown celebration was also delicious😋 Conclusion ![](/assets/blog/authors/HiroyaHinomori/IMG_2804.jpg =400x) I wanted to take more pictures, but I regret that I couldn't because I was so focused on work... By joining as a staff member, I was able to encounter new people and experiences that I never would have gotten by joining as a participant, which made me feel a sense of fulfillment. I feel that it was a great experience. I'd love to join as staff again if I get the chance next time! If you are reading this article, I encourage you to challenge yourself and consider being a conference staff member as well! Finally, I'd like to say THANK YOU to all the organizers, speakers, and other participants!!! See you again👍
アバター
はじめに こんにちは。KINTOテクノロジーズモバイルアプリケーション開発グループの Rasel です。私は現在、 my route Androidアプリの開発に取り組んでいます。 my route は、外出時に利用するマルチモーダルアプリで、目的地の情報収集、地図上のさまざまな場所の探索、デジタルチケットの購入、予約、乗車料金の支払い処理などを行うことができます。 いまやモバイルアプリは私たちの日常生活に欠かせないものです。我々のようなエンジニアは、AndroidとiOSアプリをそれぞれ別で作成するため、両方のプラットフォームを開発するためにはダブルコストが発生します。これらの開発コストを削減するためにReact Native、Flutterなど、様々なクロスプラットフォームフレームワークが登場しました。 しかし、クロスプラットフォームアプリのパフォーマンスには常に課題があります。ネイティブアプリのようなパフォーマンスではありません。また、プラットフォーム固有の新機能がAndroidやiOSからリリースされると、フレームワーク開発者からサポートを受けなければいけない場合があり、さらに時間がかかります。 そこで Kotlin Multiplatform (KMP) が助けになります。ネイティブアプリ並みのパフォーマンスで、プラットフォーム間で共有するコードを自由に選択できるのです。KMPでは、Androidのネイティブ第一言語であるKotlinでAndroidアプリが開発されていて、完全にネイティブなので、パフォーマンス上の問題はほとんどありません。iOSの部分は Kotlin/Native を使用しており、他のフレームワークと比較して、ネイティブアプリとして開発されたものに近いパフォーマンスがあります。 本記事では、SwiftUIコードをCompose Multiplatformと統合する方法を紹介します。 KMP(モバイルプラットフォームではKMMとしても知られています)では、プラットフォーム間で共有するコードの量と、ネイティブアプリに実装するコードを自由に選択でき、プラットフォームのコードとシームレスに統合されます。以前は、ビジネスロジックのみをプラットフォーム間で共有できましたが、今では UI コードも共有できるようになりました。 Compose Multiplatform においても、 UIコードの共有が可能になりました。下にある以前の記事を読むと、モバイルアプリ開発におけるKotlin MultiplatformとCompose Multiplatformの使用法をよりよく理解できます。 Kotlin Multiplatform Mobile (KMM)を使ったモバイルアプリ開発 Kotlin Multiplatform Mobile(KMM)およびCompose Multiplatformを使用したモバイルアプリケーションの開発 それでは、始めましょう! 概要 我々はUI開発でCompose Multiplatformを使用するKMPを用いてアプリ開発をしています。今回はSwiftUIをCompose Multiplatformに統合する方法を示すため、とてもシンプルなGeminiチャットアプリを使用します。また、チャットでのユーザーのクエリへの返信には、Googleの Gemini Pro APIを使用します。デモすることが目的なので、シンプルにするためにも、テキストメッセージのみが許可されるよう無料版の API を使用します。 Compose と SwiftUI がどのように連携するか まず、最初に大事なことから。Jetbrainの Kotlin Multiplatform Wizard を使用してKMPプロジェクトを作成します。このウィザードには、必要になるKMPの基本的なセットアップと、Compose Multiplatformと、いくつかの初期SwiftUIコードが付属しています。 ![Kotlin MultiplatformWizard](/assets/blog/authors/ahsan_rasel/kmp_wizard.png =450x) Kotlin Multiplatform Mobile pluginをインストールして、 Android Studio IDE を使用し、プロジェクトを作成することもできます。 ComposeとSwiftUIがどのように連携するかをデモしてみます。ComposableコードをiOS に組み込むには、Composableコードを ComposeUIViewController 内にラップする必要があります。 ComposeUIViewController は UIKit から UIViewController の値を返し、その中にComposeコードの組み立てをコンテンツパラメータとして含めることができます。 例: // MainViewController.kt fun ComposeEntryPoint(): UIViewController { return ComposeUIViewController { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text(text = "Hello from Compose") } } } 次に、この関数を iOS 側から呼び出します。そのためには、SwiftUI のComposeコードを表す構造が必要です。以下のコードは、共有モジュールであるUIViewController コードを SwiftUI ビューに変換します。 // ComposeViewControllerRepresentable.swift struct ComposeViewControllerRepresentable :UIViewControllerRepresentable { func updateUIViewController(_ uiViewController:UIViewControllerType, context:Context) {} func makeUIViewController (context:Context)-> some UIViewController { return MainViewControllerKt.ComposeEntryPoint() } } ここで、 MainViewControllerKt.ComposeEntryPoint() の名前を詳しく見てみましょう。これが Kotlin から生成されたコードになります。そのため、共有モジュール内のファイル名とコードによって異なる場合があります。共有モジュール内のファイル名が Main.ios.kt で、 UIViewController returning function nameが ComposeEntryPoint() の場合、 Main_iosKt.ComposeEntryPoint() のように呼び出す必要があります。そのため、コードによって異なります。 次に、この ComposeViewControllerRepresentable をコード ContentView() の内部からインスタンス化します。これで準備は完了です。 // ContentView.swift struct ContentView:View { var body: some View { composeViewControllerRepresentable () .ignoresSafeArea (.all) } } コードを見てわかるように、このComposeコードは SwiftUI 内のどこでも使用でき、SwiftUI 内で好きなようにサイズを制御できます。UI は次のようになります: ![Hello from Swift](/assets/blog/authors/ahsan_rasel/swiftui_compose_1.png =250x) SwiftUI のコードをCompose内に統合したい場合は、 UIView でラップする必要があります。SwiftUIのコードをKotlinで直接記述することはできないため、Swiftで記述してKotlin関数に渡す必要があります。これを実装するために、 関数 ComposeEntryPoint() に、 UIView タイプの引数を追加してみましょう。 // MainViewController.kt fun ComposeEntryPoint(createUIView: () -> UIView): UIViewController { return ComposeUIViewController { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { UIKitView( factory = createUIView, modifier = Modifier.fillMaxWidth().height(500.dp), ) } } } そして CreateUIView を以下のような Swift コードへ渡します。 // ComposeViewControllerRepresentable.swift struct ComposeViewControllerRepresentable : UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} func makeUIViewController(context: Context) -> some UIViewController { return MainViewControllerKt.ComposeEntryPoint(createUIView: { () -> UIView in UIView() }) } } さて、他のViewを追加したい場合は、以下のように親ラッパー UIView を作成してください: // ComposeViewControllerRepresentable.swift private class SwiftUIInUIView<Content: View>: UIView { init(content: Content) { super.init(frame: CGRect()) let hostingController = UIHostingController(rootView: content) hostingController.view.translatesAutoresizingMaskIntoConstraints = false addSubview(hostingController.view) NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: topAnchor), hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor), hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 次に、それを ComposeViewControllerRepresentable に追加し、必要に応じてViewを追加します。 // ComposeViewControllerRepresentable.swift func makeUIViewController(context: Context) -> some UIViewController { return MainViewControllerKt.ComposeEntryPoint(createUIView: { () -> UIView in SwiftUIInUIView(content: VStack { Text("Hello from SwiftUI") Image(systemName: "moon.stars") .resizable() .frame(width: 200, height: 200) }) }) } 出力は次のようになります: ![Hello from Swift with Image](/assets/blog/authors/ahsan_rasel/swiftui_compose_2.png =250x) この方法では、共有の合成可能なコードに、好きなだけSwiftUIコードを追加できます。 また、 UIKit コードをCompose内に統合したい場合、中間コードを自分で作成する必要はありません。Compose Multiplatformが提供するComposable関数 UIKitView () を使用して、その中にUIKitコードを直接追加できます。 // MainViewController.kt UIKitView( modifier = Modifier.fillMaxWidth().height(350.dp), factory = { MKMapView() } ) このコードは iOS ネイティブのマップ画面をCompose内に統合します。 Gemni Chatアプリの実装 それでは、ComposeコードをSwiftUI内に統合して、 Gemini Chat アプリの実装を進めましょう。Jetpack Compose の LazyColumn を使用して、基本的なチャット UI を実装します。Compose Multiplatform内にSwiftUIを統合することが主な目的なので、Composeやデータ、ロジック等、他の部分の実装についてはここでは割愛します。Gemini Pro APIを実装するため、我々はKtorネットワーキングライブラリを利用しました。Ktorの実装についての詳細は、 Creating a cross-platform mobile application のページをご覧ください。 このプロジェクトでは、Compose Multiplatformで全てのUIを実装しました。Compose MultiplatformのTextFieldではiOS側でパフォーマンスに問題があるので、iOSアプリの入力フィールドにのみSwiftUIを使用します。 ComposeEntryPoint() 関数の中にComposeコードを入れてみましょう。これらのコードには、TopAppBarを含むチャットUIとメッセージのリストが含まれています。これには、Androidアプリで使用される入力フィールドの条件付き実装もあります。 // MainViewController.kt fun ComposeEntryPoint(): UIViewController = ComposeUIViewController { Column( Modifier .fillMaxSize() .windowInsetsPadding(WindowInsets.systemBars), horizontalAlignment = Alignment.CenterHorizontally ) { ChatApp(displayTextField = false) } } false を displayTextField に渡したので、iOS バージョンのアプリでは Compose 入力フィールドがアクティブになりません。そして、Android側のTextFieldにはパフォーマンスの問題がないため、Android 実装側からComposable関数をこの ChatApp () のComposable関数を呼び出すと、 displayTextField の値は true で返ってきます。(これはAndroid のネイティブ UI コンポーネントです。) それでは、Swift コードに戻ってSwiftUIで入力フィールドを実装します。 // TextInputView.swift struct TextInputView: View { @Binding var inputText: String @FocusState private var isFocused: Bool var body: some View { VStack { Spacer() HStack { TextField("メッセージを入力する...", text: $inputText, axis: .vertical) .focused($isFocused) .lineLimit(3) if (!inputText.isEmpty) { Button { sendMessage(inputText) isFocused = false inputText = "" } label: { Image(systemName: "arrow.up.circle.fill") .tint(Color(red: 0.671, green: 0.365, blue: 0.792)) } } } .padding(15) .background(RoundedRectangle(cornerRadius: 200).fill(.white).opacity(0.95)) .padding(15) } } } そして、 ContentView 構造体に戻り、以下のように修正します: // ContentView.swift struct ContentView: View { @State private var inputText = "" var body: some View { ZStack { Color("TopGradient") .ignoresSafeArea() ComposeViewControllerRepresentable() TextInputView(inputText: $inputText) } .onTapGesture { // Hide keyboard on tap outside of TextField UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } } } ここでは ZStack を追加し、その中に TopGradient カラーと、Modifier ignoresSafeArea () を追加して、ステータスバーの色が他の UI の色と一致するようにしました。 次に、共有されたCompose コードのラッパー ComposeViewControllerRepresentable を追加し、メインのチャットUIを実装しました。そして、 TextInputView() というSwiftUIビューも追加しました。これにより、iOSアプリのユーザーにもiOSネイティブコードでスムーズなパフォーマンスが提供することができます。最終的なUIは次のようになります。 Gemini Chat iOS Gemini Chat Android ![Gemini Chat iOS](/assets/blog/authors/ahsan_rasel/swiftui_compose_ios.png =300x) ![Gemini Chat Android](/assets/blog/authors/ahsan_rasel/swiftui_compose_android.png =300x) ここでは、ChatAppのUIコード全体がKMPのCompose MultiplatformでAndroidとiOSの両方に共有され、iOSの入力フィールドのみがSwiftUIにネイティブに統合されています。 このプロジェクトの完全なソースコードは、GitHub で公開リポジトリとして公開されています。 GitHubリポジトリ:Compose MultiplatformにおけるSwiftUI さいごに このように、Kotlin Multiplatformと Compose Multiplatform を使うことで、クロスプラットフォームアプリでのパフォーマンスの問題を解決しながら、ユーザーにネイティブのような操作感と外観を提供できます。また、プラットフォーム間でコードを好きなだけ共有できるため、開発コストも削減できます。Compose Multiplatformでは、デスクトップアプリとコードを共有することもできます。ですから、単一のコードベースをデスクトップアプリだけでなくモバイルプラットフォームでも使用できます。さらに、プラットフォーム間でのコードベース共有を促進するため、Webサポートも進行中です。Kotlin Multiplatform (KMP) のもう1つの大きな利点は、コードを無駄にすることなく、いつでもネイティブ開発に切り替えることができる点です。AS-ISのKMPコード はAndroidネイティブのため、Androidアプリではそのまま利用でき、iOSアプリを別途切り離して開発することができます。また、KMPにすでに実装したものと同じSwiftUIコードを再利用することも可能です。このフレームワークは、高性能のアプリケーションを提供するだけでなく、共有するコードの割合を自由に変更したり、ネイティブ開発にいつでも切り替えたりできます。 本記事はここまでとしますが、KINTOテクノロジーズのテックブログでは今後もおもしろい記事を発信していきます!Happy Coding!
アバター
Introduction Hello! My name is Morimoto, and I am a backend engineer at KINTO Technologies. I am part of the KINTO ONE development Group where I primarily use Java for KINTO ONE. But this time, I would like to introduce a study session of GraphQL that we're conducting separately from our regular work. What is GraphQL? GraphQL is a query language. Unlike other languages such as SQL, GraphQL can interact with multiple data sources, not just a specific one. If the schema is defined on the backend side, the frontend side can freely retrieve the items in the object according to the definition. Unlike the REST API, with GraphQL, you have the flexibility to specify what information the frontend wants to return from the backend. There is no need to get unnecessary information, and there is no need to call the API multiple times to get nested objects. Purpose of the Study Group There were two main purposes: To improve our technical skills To interact beyond our respective teams To Improve Technical Skills We wanted to catch up with new information in addition to the technology we use in our daily work, but each team member felt that it was a high hurdle to overcome alone. For example, our lack of language knowledge could be cited as a barrier. The GraphQL tutorial we decided to follow used Typescript. So we had to learn Typescript first before learning GraphQL. The idea was that by supplementing each other with our different knowledge and experiences, we could overcome challenges and make the learning curve less steep. To Interact Beyond Our Respective Teams We also wanted to make it as an opportunity for members -regardless of group, team, project or different ages-, to interact with each other. Many of us were good friends who already knew each other, but we were determined to get along better by getting together on a regular basis. I also thought that the study session would be an opportunity to learn about new aspects of each other. Content Details Why GraphQL? Those who typically implemented APIs on the backend were struggling with the need to create an API every time a requirement came. Of course, there are times that is faster to process data on the server side, but it is troublesome to increase the number of APIs that return information as it is. As for me, ever since I heard that GraphQL was a good solution for this, I wanted to try it out. Some members already had some experience using GraphQL, but they wanted to understand the overall process flow, so we decided to properly study it together. Tutorials Used for the Study Sessions We chose Apollo GraphQL for the GraphQL library, and used the tutorial linked below. GraphQL Tutorials The reason why we chose it is due to the volume of tutorials available and we felt it was a good introduction. In addition, one of the study group members had used Apollo GraphQL in their work, so we knew there was a track record of being used within the company. Summary of the Study Group Date and Time It was held once a week after 6pm when members had time. Members The group consists of eight young members ranging from 25 to 28 years old. Our background and expertise was diverse, each belonging to different domains such as web application frontend, backend, as well as mobile application frontend and backend. What We Did We completed all five chapters of the tutorial, from Lift-off I to V. It describes the basics of implementing GraphQL. How It Was Conducted and What We Arranged We conducted the study group following the below flow: We opted to go through the tutorials in Mokumoku-kai style. We reinforced our learning by doing presentations to each other of the content from the tutorials we reviewed. We started by holding our series of Mokumoku-Kai . Mokumoku-Kai is a study group method where everyone gathers, sharing questions and ideas when needed, but mainly focuses on their own. As mentioned, some had used Apollo GraphQL before, but none had a complete picture of the process. For that reason, we first completed the same tutorials and then discussed and resolved any points that came up. However, some mentioned they were doubtful if they really understood it, and that maybe it was good to refresh concepts first before moving on. So we decided to present each section to one another on a rotating basis. The presentation format required presenters to understand the tutorial perfectly. At the study session, there were moments where, upon reviewing, we found answers to questions in parts where we had been progressing somewhat aimlessly. During the presentation however, we could ask questions, change the source code and try it out, and there were new discoveries that one would not have found on their own. A glimpse at one of our sessions. In the foreground are boxes of sandwiches prepared for the study group. Conclusion First and foremost, we achieved a deep understanding of GraphQL thanks to these sessions. By using our knowledge and experience to complement each other, we were able to proceed faster and more reliably than anyone could on their own. Having study partners also helped us to persevere through moments when we felt like giving up. We aim to continue with the remaining chapters of Apollo GraphQL tutorials and learn more about other technical topics. We even discussed how we would love to create some kind of application in the process. By exploring the languages, frameworks, and architectures each of us is interested in, I hope to keep getting better with my technical capabilities.
アバター
ごあいさつ 皆さまこんにちは。テックブログチーム改め技術広報グループの森です。 実はこの4月より、テックブログチームは「技術広報グループ」として生まれ変わりました✨ 今後ともよろしくお願いします🙇‍♀️ 技術広報以外のお仕事は別記事で書いておりますので、もしご興味あればぜひご一読ください 👀 KINTOのグローバル展開におけるGDPR等個人データ関連法対応 GDPR対応! Cookie同意ポップアップをグローバルサイトに設置した話 導入 2024年1月31日、KINTOテクノロジーズ(KTC)では初となるの全社オフラインミーティングを開催いたしました🎉 2024年のKick Offという位置づけです。実はこのイベント、完全ボトムアップで企画・運営されました。この大規模ミーティングがどのように作られたか、本記事で裏側をご紹介します。今後のための備忘録のようなものですが、「自社で内製イベントすることになったけどどうしよう!?」という方に少しでも参考になれば何よりです。 本来ならすぐにレポートするところを、私の遅筆により約半年後の記事公開となってしまったこと、お許しください🙇‍♀️ (イベント運営の記事は鮮度が大事なのに… 😭) 企画のきっかけ コロナ禍中に弊社従業員数は爆増し、いまや約350名の社員が所属しています。 この規模になるとやはり横の繋がりや一体感を生み出すことはなかなか難しく、以前よりオフラインイベントやチームビルディングイベントを求める声が多くありました。また、トップ層からのメッセージ発信の場も多くはないので、全体ビジョンの浸透には時間を要していました。 そういった課題を踏まえ、「アフターコロナだし、全社員が集まれる機会があれば少しはこの課題もクリアになるかも」とイベント運営によく携わる3名で企画が始まりました。これが11月初旬のお話。 まずは大枠を 11月に3人で企画を開始したのですが1月開催なので実施まで3ヵ月しか期間がなく、スケジュールはかなりタイトでした。 ラフなスケジュールを以下のように引いて進めることになりました。 まずは開催すること自体に賛同を得るため、企画の大枠を以下のように検討しました。 開催目的 2023年1年の総括と2024年のキックオフ 共通のビジョンを共有すること・他部署間交流による組織の一体感醸成 企画内容 毎月の全社員ミーティング(開発編成本部会)の拡大版 前半はオンライン参加可能(業務内) 懇親会はオフライン参加のみ(業務外) コンテンツ Category Time Contents Note リハ 15:00-16:00​ 会場設営/リハーサル 音響準備や進行の調整など 16:00​-16:30​ 入場開始〜受付​ 参加者の受付 本編 16:30-16:35​ 開場〜オープニング 16:35​-16:40​ 2023年の振り返り​​(副社長) 2023年の振り返りと2024年の展望 をシェア 16:40-17:30​ 2023年の漢字​​ 2022年末にも実施しました。各グループの振り返りコーナー 17:30​-17:40​ 休憩 / プレゼン準備​​​ 17:40-18:35​ K-1グランプリ​​​​ 各部2023年の代表案件をプレゼンし、表彰! 18:35​-18:45​ 休憩​​​​​ 18:45-19:00​​ K-1グランプリ 結果発表​​​​​​ 表彰と受賞者からのコメント 19:00​-19:05​ 総括​と2024年に向けて(社長) 2023年総括と2024年への期待をシェア 懇親会 19:05-19:20​ 写真撮影 / 休憩 / 転換​ 19:20​-20:50​ 懇親会​​ ・乾杯+鏡開き ・ミニゲームも入れて全社交流の時間!​ 20:50-21:00​​ 撤収作業​​​ 21:00完全退出​ 各グループを巻き込め! 大枠が決定したので、全体の人数を把握すべく社内に公示しました。 普段の社内イベントはSlackで全社に向けて一度アナウンスすることが多いのですが、今回はなにせ全社イベント。各グループの協力なくしては統率が取れません🤦‍♀️ そこで、各グループから担当者を立てていただき、各グループの取りまとめをお願いしました。 普段は何度も何度も運営からアナウンスしないとなかなか回収しきれない回答も、各グループ担当者に取りまとめていただいたことで比較的スムーズに、〆切までに回収することができました。各G担当の皆様、本当にありがとうございました!大感謝 😭❤️ ![announce](/assets/blog/authors/M.Mori/20240611/announce.png =500x) 私の部での告知の様子 想像以上のオフライン参加率! 今回のイベントは開発編成本部会、つまり全社員ミーティングという建付けですので、基本は全員参加必須です。 家庭の都合や出張などでどうしてもオンライン参加になる方もいらっしゃいますが、それでも300名規模の会場が必要でした。 オフィス近郊での会場探しはかなり苦戦しましたが、片っ端から検索しては電話を繰り返し、奇跡的に神保町オフィスから徒歩5分の 「神田スクエアホール」 を手配することができました。 ![Hall](/assets/blog/authors/M.Mori/20240611/square_hall.jpg =500x) とってもきれいな会場。神田スクエア様、ありがとうございます。 やむを得ずオンライン参加になった方や英語通訳チャネル(後述)のため、本部会パートはWebinar配信も行いました。配信担当の方々、本当にいつもありがとう😭❤️の気持ちです。 各担当で並行してタスクを遂行! イベントを行う際は運営チームを分けてそれぞれでタスクを動かします。KINTOテクノロジーズのすごいところはアサインしたらそれぞれが自走してくれるところ…!!前のめりに動いてくれたり意見してくれたりするので、非常に助かります。 今回は前述の各G代表者の中から数名を複数の役割に分けてアサインしました。 役割 タスク詳細 統括 全体の取りまとめ、各担当者が困ったときの相談役 司会 イベント全体のファシリテーション、盛り上げ(一番重要!)の施策検討 受付 誘導の流れを検討、案内すべき事項の取りまとめ 通訳 多数所属するNon-Japaneseに向けた通訳用に外部通訳者様との調整担当 今年の漢字 各Gから2023年を表す漢字・2023年の成果と2024年への意気込みを取りまとめ K-1グランプリ 各部の代表案件を取りまとめ 社長・副社長挨拶取りまとめ 社長副社長の伝えたいメッセージとイベント趣旨をすり合わせて資料を作成 懇親会 ケータリングを何にするか+懇親会で何をするかの検討 ノベルティ 全員に配布されるノベルティや景品などの作成 司会 当日の様子はまた別の記事でお伝えできると思いますが、今回は以前からイベントの司会や盛り上げをしてくれていた3名に総合司会をお願いしました。当日のタイムラインに合わせてパートの振り分けであったり、当日の流れを想定して、いつのタイミングでどういったスライドが必要か?どう盛り上げるか?などを考えてくれました。ざっくりタイムラインはあったものの、実際に司会をするにあたって気になるポイントを洗い出したり、スクリプトを作ったり。何の依頼もしていないのに「司会お願いします」と言っただけでここまでやってくれていました。感激😭❤️ ![shinko](/assets/blog/authors/M.Mori/20240611/shikai_shinko.png =500x) 進行中の気になるポイント ![Script](/assets/blog/authors/M.Mori/20240611/shikai_script.png =500x) 司会スクリプト 受付 内部イベントとはいえこれだけ多くの人数が集まるイベントとなると、手際のよい受付が非常に重要です。受付担当としてメインで5名が手を挙げてくれました。(そして当日はたくさんの人がお手伝いしてくださいました…!!!) 受付で重要なのはなんといってもいかにスムーズに案内するか!受付でイベント参加者の第一印象が決まるため、受付に人が滞留すればするほどイベントへの不満はたまっていきます。 そこで今回工夫したのは従来の出席者リストで〇xをつけるのではなく、出席者の主体性に任せ、以下の流れで受付を行いました。 予め導線を作っておくことで、受付で停滞することなく非常にスムーズに会場へ誘導することができました。 一方で、会場までの誘導が行き届いていなかったのは反省点。次回の改善点としてメモです📝 通訳 KTCは多国籍なメンバーで構成されており、英語のほうが得意なメンバーが多数所属しています。今回は2023年の総括かつ2024年のキックオフということで経営層の大事な話も入るため、本部会本編は全コンテンツ通訳を入れることになりました。しかし、2時間半にも及ぶ本編を逐次通訳するのは素人では到底無理です🤦‍♀️ そこで、以前からオリエンテーションの通訳などでお世話になっている通訳会社様にお願いすることにしました。 🔻ZOOMでの通訳は通訳機能をONにしておくと言語チャネルを切り替えられるようになっています🔻 通訳者様が耳で日本語を聞き👂、そのまま英語チャネルで英語で発話🗣️することで、英語チャネルには英語音声が流れる仕組みです。 設定の方法はこちら👉 ミーティングまたはウェビナーでの言語通訳の使用 運営チーム内の通訳担当は現地にいない通訳者様とコミュニケーションを取り、音声・映像トラブルや会場の様子などを適宜コミュニケーションします。通訳があることで、経営層のメッセージを的確に伝えることができました。プロの通訳者様には頭が上がりません🙇‍♀️ 2023年の漢字 2022年末も実施したこの企画。各グループからマネージャーが登壇し、1年を表す漢字と総括、そして新しい1年に向けた意気込みを共有します。 事前に22グループの回答を取りまとめて当日の資料に反映させる作業を担当者にお願いしました。 忙しいマネージャー陣にお願いすることになるので、12月中旬に案内、1月19日の〆切です。 ![kanji_announce](/assets/blog/authors/M.Mori/20240611/kanji_announce.png =500x) 🔻こちらは旧テックブログチーム(現技術広報グループ)のもの。 ![kanji_blog](/assets/blog/authors/M.Mori/20240611/kanji_blog.png =700x) 🔺こんな感じでConfluenceに各グループの内容をまとめていただき、 🔻こんな感じに資料に落とし込んでいきました。 ![kanji_blog_ppt](/assets/blog/authors/M.Mori/20240611/kanji_blog_ppt.png =700x) 各グループのカラーが出ていておもしろかったのと、各グループのやっていたこと・やっていくことが知れる滅多にない機会になりました! K-1グランプリ 何といっても今回の目玉企画です。弊社では毎月「景山賞」と称して特筆すべき案件や活動を表彰しています。 👉 参考記事: 全社員ミーティングをテコ入れした話 業務の振り返りと業務価値の再認識そして部署を超えた情報共有が目的ですが、これの年度賞版をK-1グランプリと称して行うことになりました。 大まかな流れは下図の通りです。 月次賞ではプレゼンは行いませんが、今回は年度賞。プレゼン力も問われます。 グループの数が多いため、まずは各グループから案件をエントリーしてもらい、その中から各部代表案件をひとつずつ選出してもらいました。 私はプラットフォーム部の選考会に賑やかしとして参加させていただいたのですが、普段違うグループで働いている メンバーを互いに称賛しあう場 になっていたのが印象的でした。 アナウンス時や予選会、当日まで通してお伝えし続けてきたのは、K-1GPは年度賞ですが、決して優劣をつけることが目的ではないということです。 この1年、皆さんが従事してきた仕事は全て素晴らしいものであることは大前提です。 K-1GPの一番の目的は自身の業務を振り返り、お互いの仕事を称賛し合うことだったので、少なくとも私の参加したプラットフォーム部の予選会では、この 「互いに称賛し合う姿」 が見られて非常にうれしかったです。 こうして予選会で選出された代表案件は、それぞれ本部会までの1週間で各3分のプレゼン資料を準備いただき当日を迎えました。 非常にタイトなスケジュールで準備をいただくことになり、代表者の皆さんには感謝感謝です🙇‍♀️ 集まっていくプレゼン資料はそれぞれ個性に溢れていて、毎日格納される資料をワクワクして待っていました。笑 社長・副社長ごあいさつ 2024年のキックオフということで、小寺社長と景山副社長からのごあいさつも大きなコンテンツでした。 毎月の全体ミーティング直接お話を聞く機会はなく、特に小寺さんに関してはKINTO/KTC合同の場でしかお話いただくことがなかったため、非常に重要な場でした。 明確なトップメッセージを全員が聞くことで同じ方向を向いて仕事をすることができます。いわば軸のようなものです。 運営メンバーで事前に「KTCのエンジニアにどのようになってほしいか」「2024年KTCにどのようなことを求めるか」をすり合わせたり、 逆にメンバー目線で「こういったことをぜひ発信いただきたい」ということをお伝えしたりして全体構成をまとめていきました。 スライドはより伝わりやすいよう、我らがデザイナー軍団クリエイティブ室にお力添えいただきました。 外国籍メンバーにも誤解の無いような言葉を選んだり、ビジュアルで補完したり。 ![president_message](/assets/blog/authors/M.Mori/20240611/president_message.jpg =500x) 社長メッセージをビジュアル化 今回トヨタの新しいビジョン 「次の道を発明しよう」 (Inventing our path forward together) がタイミング良く発表され、こちらも改めて社長よりシェアされました。 ![toyota_message](/assets/blog/authors/M.Mori/20240611/toyota_message.jpg =500x) Inventing our path forward together 懇親会 さて、オフラインイベントの醍醐味といえば懇親会です。 今回は会場指定のケータリングを利用させていただきましたが、ロゴ入りハンバーガーや飾りつけもすることができ、とても豪華になりました ✨ ![logo_burger](/assets/blog/authors/M.Mori/20240611/logo_burger.jpg =500x) ケータリングはホワイエに用意し、本会場には何も置かなかったので、ご飯や飲み物を取りに行きにくかったのは反省点です。 さて、今回の乾杯は「鏡開き」にて行いました。 運営メンバーみんな初めての生鏡開きだったので、事前に調べたところ「バールや大きなカッターが必要」と出てきて非常に焦りました。 が、なんとそんな必要のない非常にお手軽なオリジナル樽を KURAND様のサイト [^1]で発見し、こちらを採用。 [^1]: KURAND様はこのご縁もあり、後日弊社主催のイベント 「ソースコードレビュー」まつり にご協賛いただきました。 ![kagamibiraki](/assets/blog/authors/M.Mori/20240611/kagamibiraki.jpg =500x) めちゃくちゃかわいくないですか!? このオリジナルデザインはこちらも我らがクリエイティブ室のデザインです 💯 乾杯後は基本フリーではありましたが、なんといっても260人規模です。普段会話しない人とも会話してほしいのが運営の想い。 何か話のきっかけにできるものを検討しました。 当初はチーム分けしてゲームするか?と話していましたが、大人数すぎるし、強制参加もさせたくないし...と悩んでいたところで運営が見つけたのが Rally でした。 スマホで簡単にスタンプラリーができるサービスです。QRを読み込んでスタンプラリーができるので、このQRを各部ごとに配布すれば交流ができるのでは...!?即決でした。 フリープランでもいろいろとカスタマイズでき、1週間でけっこうな完成度のものができました。 🔻Rallyの使い方はこんな感じ。 ![rally_slides](/assets/blog/authors/M.Mori/20240611/rally_slides.jpg =700x) 受付で配布したQRコードシールが各自のIDケースに貼られているので、それを読み取ってスタンプを集める形式です。 準備の手軽さとコミュニケーションの促進という意味では非常に良かったです。非常に良かった。 強制参加させることもなく、スムーズに違う部署の人に声をかけあってる姿がもはや感動的でした。 ![rally_poster](/assets/blog/authors/M.Mori/20240611/rally_poster.jpg =500x) 当日掲示したポスター ノベルティ さて、事前準備編ということでもう一つ忘れてはいけない準備物がノベルティです。 タイトなスケジュールだったため、必要なものを最初に洗いだせておらず、クリエイティブ室の皆様にはかなり無理を言ってたくさんのものを作っていただきました。。 K-1 GPロゴ 表彰状 ![idcase](/assets/blog/authors/M.Mori/20240611/design_k1_logo.png =300x) ![award](/assets/blog/authors/M.Mori/20240611/design_award.jpg =300x) スライドマスタ 鏡割り用の樽デザイン ![slidemaster](/assets/blog/authors/M.Mori/20240611/slide_master.jpg =300x) ![sakadaru](/assets/blog/authors/M.Mori/20240611/design_sakadaru.png =300x) IDカードケース(全員配布) スタッフTシャツ ![idcase](/assets/blog/authors/M.Mori/20240611/design_idcase.jpg =300x) ![staff_shirts](/assets/blog/authors/M.Mori/20240611/design_staff_t.jpg =300x) タンブラー(スタンプラリー景品) エコバッグ (スタンプラリー景品) ![tumbler](/assets/blog/authors/M.Mori/20240611/design_tumbler.jpg =300x) ![eco_bag](/assets/blog/authors/M.Mori/20240611/design_bag.jpg =300x) 改めて見ても「どんなけ作らせるねん!?」とツッコミたくなるレベルですね。笑 これに加えて社内エンジニアには各自の名札を自動で作成できるツールを作成してもらいました。 🔻Slackアイコン・部署・名前・KTCロゴが全員分印字されます。 ![Name_card](/assets/blog/authors/M.Mori/20240611/namecard.jpg =300x) 「こんなのあったらいいな」と軽く言ってみたらほんとにすぐに作ってくれました。 自社ながら、KTCメンバーの仕事の速さとクオリティの高さには毎度驚かされます。 本業がある中でもご協力いただいた方々にこの場をお借りして改めて深く感謝します 🙇‍♀️🙇‍♀️🙇‍♀️ 運営してみた学び・次回開催に向けて もう半年も経ちましたが、こうしてやったことを書き出してみると、よく準備したなぁ…笑 今回の記事執筆でこのキックオフ会をふり返ってみて、改めて「組織のビジョンや目標をわかりやすく全社に共有すること」「オフラインでチームビルディングを行うこと」の重要性を認識しました。 経営層から直接ビジョンや戦略が伝えられるだけで、その考えやダイレクションに基づいて同じ方向を向いて日々職務に従事することができます。また、この考えに共感できれば、社員のモチベーションアップにもつながります。これをオフラインで行うことにより、そのダイレクションは浸透しやすくなり、社員と経営層、さらには社員同士にも信頼関係が生まれ、疑問や不安の解消にも役立ちます。 特に弊社はKINTOサービススタートから5年経ち、会社としても次のステージに向かう最中。このタイミングでこういったイベントを行うことが、組織全体のエンゲージメント向上や、一体感の醸成に繋がるのだと実感しました✨ また別の記事などで実施結果もお伝えできると思いますが、参加者の声としても「仕事へのモチベーションが上がった」「他のチームが何をしているか、認識が強まった」「経営層の考えを知ることができた」など非常に好意的な反応が多く、実施した甲斐があったな、と思いました😄 こういったイベントはぜひ1年に一度は開催したく、次回開催に向けて運営の学びを活かし、至らない点は反省点としてさらなる改善を目指します💪 気づけば7000文字以上も書いてしまいましたが、それだけ思い入れのあったイベントだったということで。。 最後まで読んでいただきありがとうございます!KINTOテクノロジーズでは今後も社内外様々なイベントを計画中です! 社外向けイベントは 弊社Connpass にて随時募集しますので、ご興味あればぜひご参加ください 😄
アバター
Hello Hi there, my name is Murayama, and I work as an assistant at the CIO office at KINTO Technologies. This article will introduce our employees' office and desk setups in a relaxed manner (˘ω˘) Introduction to Our Offices This is our head office in Nagoya. President Kotera-san’s strong vision is reflected in the interior, emphasizing natural elements and brightness. The fire pit you can see in the bottom right picture -which is Kotera-san's particular point of focus-, is lit during certain times. It's located in the center of the office, where everyone gathers to have lunch together! The second location is the Muromachi office. Our Muromachi office located in Tokyo has two floors. It also has this area we call “the Junction”. It's a very elegant spot, also used for video and photoshoots! It's conveniently located near many shops since it's housed inside of the COREDO Muromachi 2 building. In this area, you can find whatever you want to eat! The third location is the Jimbocho office. I saw the Platform Group gathered in the big conference room so I took a picture of them. The Jimbocho office is popular because it has the largest number of conference rooms. This area offers affordable lunch options, especially there are a lot of delicious curry restaurants! I always have curry whenever I visit 🍛 The photo in the bottom right corner is a vending machine with the KINTO Technologies logo at this office. The fourth site is the Osaka Tech Lab. Not ‘office’, but ‘Tech Lab’! (This is important) It opened in April and has still few employees, but everyone there shares their opinions to improve it. The rooftop in the bottom right part of this photo is wide and popular. Lunch is also cheap around Shinsaibashi! Plus, Osaka's batter-based dishes are delicious! Although I'm from Kanto, so I’m not used to okonomiyaki set meals for lunch... Introduction to Our Desk Setups Each person personalizes their seat to work comfortably. Functional desks reflect having a good setup, I’m sure, but I don’t think its only about functionality. This is mine. I have a big cheering squad. It's a great desk setup, right?! Our vice president Kageyama-san also has some on his desk. Every once in a while, one of them rolls off somewhere and I find it heartwarming and funny to see Kageyama-san search for it. It's inevitable when you hold a Sylvanian Families figurine in your hands, it brings out your nurturing instincts. Before I make this blog all about Sylvanian Families, let's move on to the next desk around here. ![Employee commentary](/assets/blog/authors/uka/member-02.jpg =450x) Cool keyboard! She enjoys building her own PCs and Gunpla. Great hobby! In her home desk setup, she has many Gunplas watching her. She seems to have also brought a small one into the office today. I gave her a Sylvania so she has even more friends now. By now, I'm one of the Sylvanian Families evangelists in office! ![Employee commentary](/assets/blog/authors/uka/member-03.jpg =450x) I'm sharing all this informally, but please know that I also perform well at my job. There’s an e-sports club in the KINTO Technologies community, and we all played Splatoon together the other day. As I work at an IT company, I naturally (I guess?) love games as well. The recent trend in the company is playing Mahjong! ![Employee commentary](/assets/blog/authors/uka/member-04.jpg =450x) Se says she wants Doraemon's Anywhere Door and I can relate. I wish I could easily travel back and forth between the different offices... But setting wishes aside, whenever I am needed, I travel to the other offices too. Each office has its own good points and I enjoy working in all of them! ![Employee commentary](/assets/blog/authors/uka/member-05.jpg =450x) There are many people here who like books and the company has a system to lend them but It's also common to see employees lending books to each other. Also, I learned about Slack after joining KINTO Technologies. It's a wonderful application filled with cute emoji's and it allows us to communicate with each other in a nice and informal way! ![Employee commentary](/assets/blog/authors/uka/member-06.jpg =450x) This setup is super engineer-like, with its double display!! It's a wonderful desk with both functional aspects and modest comfort. By now, you should understand that Sylvanian Families are universally appealing, right? Finally Remote work is popular these days, but I think it is best to go to office and work with everyone face to face in an atmosphere that you enjoy (˘ω˘) On top of that, you are free to change your hair color, clothes, and desk setup, allowing you to work in a comfortable environment, which makes it more enjoyable! Thank you for reading till the end!
アバター
こんにちは。 DBRE チーム所属の @hoshino です DBRE(Database Reliability Engineering)チームでは、横断組織としてデータベースに関する課題解決や、組織のアジリティとガバナンスのバランスを取るためのプラットフォーム開発などを行なっております。DBRE は比較的新しい概念で、DBRE という組織がある会社も少なく、あったとしても取り組んでいる内容や考え方が異なるような、発展途上の非常に面白い領域です。 弊社における DBRE チーム発足の背景やチームの役割については「 KTC における DBRE の必要性 」というテックブログをご覧ください。 この記事では、DBREチームが運用しているリポジトリに PR-Agent を導入した際に、どのような改善が見られたかについてご紹介します。また、プロンプトを調整することで、コード以外のドキュメント(テックブログ)のレビューにも PR-Agent を活用した事例についても説明します。少しでも参考になれば幸いです。 PR-Agent とは? PR-Agent は、ソフトウェア開発プロセスを効率化し、品質向上を目指す自動化ツールです。主な目的はプルリクエスト(PR)の一次レビューを自動化し、開発者がコードレビューに費やす時間を削減することです。自動化されることで、迅速なフィードバックが提供されることも期待できます。 また、他のツールと異なる点として利用できるモデルが豊富なのも特徴です。 PR-Agent は複数の機能(コマンド)を持っており、どの機能を PR に対して適用するかを開発者が選択できます。 主な機能は以下の通りです。 Review コードの品質を評価し、問題点を指摘するレビュー機能 Describe プルリクエストの変更内容を要約し、概要を自動生成する機能 Improve プルリクエストで追加・変更されたコードの改善点を提案する機能 Ask プルリクエスト上で AI とコメント形式で対話し、PRに関する質問や疑問を解消する機能 詳しくは 公式ドキュメント をご参照ください。 なぜ PR-Agent を導入したか DBRE チームでは、AI を活用したスキーマレビューの仕組みを PoC(概念実証)として進めていました。その過程で、レビュー機能を提供するツールを以下の観点で調査しました。 インプット KTC における Database スキーマ設計のガイドラインを元にスキーマレビューすることは可能か 回答精度を向上させる目的で、LLM へのインプットをカスタマイズ(Chainや独自関数の組み込み等)できるか アプトプット レビュー結果を GitHub にアウトプットするために、LLM からのアウトプットを元に以下の条件が実現可能か PR をトリガーにレビューを実施できるか PR に対してコメントが可能か PR 上のコード(スキーマ情報)に対して生成 AI からの出力をコメント可能か コード単位で修正案を提示できるか 調査の結果、インプットの部分で要件に完全に合致するツールは見つかりませんでした。 しかし、調査を進める中で、DBRE チーム内の検証で使用した AI レビューツールの一つを実験的に導入してみようという意見が出され、最終的に PR-Agent を導入しました。 調査を行ったツールのなかで PR-Agent を導入した主な理由は以下のとおりです。 オープンソースソフトウェア(OSS)であること コストを抑えながら導入することが可能 使用できるモデルの豊富さ 様々な AI モデルに対応しており、ニーズに合わせたモデルを選択して使用できる点が魅力 導入の容易さとカスタマイズ性 導入が比較的容易で、設定やカスタマイズが柔軟に行えるため、チームの特定の要件やワークフローに合わせて最適化することが可能 今回は Amazon Bedrock を使用しています。使用した理由は以下のとおりです。 KTC は主に AWS を活用しており、スピード感を持って導入できる Bedrock でまずは試すことにした OpenAI の GPT-4 と比べて、Claude3 Sonnet を利用することで金銭的コストが 1/10 ほどに抑えられる 以上の理由から、DBRE チームのリポジトリに PR-Agent を導入しました。 PR-Agentの導入時に実施したカスタマイズ 基本的には、公式ドキュメントに記載されている手順をもとに導入しています。当記事ではカスタマイズした内容を具体的にご紹介していきます。 Amazon Bedrock Claude3 を利用 使用するモデルは Amazon Bedrock Claude3-sonnet を利用しています。 公式ドキュメント ではアクセスキーによる認証方式が推奨されていますが、社内のセキュリティ規則に準拠するという観点で、ARNによる認証方式を採用しました。 - name: Input AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN_PR_REVIEW }} aws-region: ${{ secrets.AWS_REGION_PR_REVIEW }} GitHub の Wiki でプロンプトを管理 DBRE チームでは複数のリポジトリを運用しているため、プロンプトの参照元を一元管理する必要があります。また、PR-Agent 導入直後には、チームメンバーが簡単にプロンプトを編集し、プロンプトチューニングを行える環境を整えたいと考えました。 そこで検討したのが GitHub Wiki の活用です。 GitHub Wiki は変更ログが残り、誰でも手軽に変更ができるため、これを利用することでプロンプトの変更をチームメンバーが容易に行えると考えました。 PR-Agent では、describe などの各機能に対して、追加の指示を extra_instructions という項目に GitHub Actions で設定することができます。 ( 公式ドキュメント ) # configuration.toml の内容を抜粋 [pr_reviewer] # /review # extra_instructions = "" # 追加の指示を記載 [pr_description] # /describe # extra_instructions = "" [pr_code_suggestions] # /improve # extra_instructions = "" そこで、GitHub Wiki に記載されているプロンプトを、PR-Agent が設定された GitHub Actions 内で変数を通じて、追加の指示(プロンプト)を動的に加えるカスタマイズを行いました。 以下、設定手順となります。 まず、任意の GitHub アカウントで Token を発行し、GitHub Actions を使って Wiki リポジトリをクローンします。 - name: Checkout the Wiki repository uses: actions/checkout@v4 with: ref: main # 任意のブランチを指定(GitHub の default は master) repository: {repo}/{path}.wiki path: wiki token: ${{ secrets.GITHUB_TOKEN_HOGE }} 次に、Wiki の情報を環境変数に設定します。ファイルの内容を読み込み、プロンプトを環境変数に設定します。 - name: Set up Wiki Info id: wiki_info run: | set_env_var_from_file() { local var_name=$1 local file_path=$2 local prompt=$(cat "$file_path") echo "${var_name}<<EOF" >> $GITHUB_ENV echo "prompt" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV } set_env_var_from_file "REVIEW_PROMPT" "./wiki/pr-agent-review-prompt.md" set_env_var_from_file "DESCRIBE_PROMPT" "./wiki/pr-agent-describe-prompt.md" set_env_var_from_file "IMPROVE_PROMPT" "./wiki/pr-agent-improve-prompt.md" 最後に、PR-Agent のアクションステップを設定します。各種プロンプトの内容を環境変数から読み込みます。 - name: PR Agent action step id: pragent uses: Codium-ai/pr-agent@main env: # model settings CONFIG.MODEL: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 CONFIG.MODEL_TURBO: bedrock/anthropic.claude-3-sonnet-20240229-v1:0 CONFIG.FALLBACK_MODEL: bedrock/anthropic.claude-v2:1 LITELLM.DROP_PARAMS: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} AWS.BEDROCK_REGION: us-west-2 # PR_AGENT settings (/review) PR_REVIEWER.extra_instructions: | ${{env.REVIEW_PROMPT}} # PR_DESCRIPTION settings (/describe) PR_DESCRIPTION.extra_instructions: | ${{env.DESCRIBE_PROMPT}} # PR_CODE_SUGGESTIONS settings (/improve) PR_CODE_SUGGESTIONS.extra_instructions: | ${{env.IMPROVE_PROMPT}} 以上の手順で、Wiki 上に記載されているプロンプトを PR-Agent に渡し、実行することが可能となります。 レビュー対象をテックブログへ拡張するために実施したこと 弊社のテックブログは Git リポジトリで管理されています。そのため、PR-Agent を利用してブログ記事も同様にレビューできないかという意見がありました。 通常、PR-Agent はコードレビューに特化したツールです。試しにブログ記事をレビューしてみたところ、Describe および Review 機能はある程度機能しましたが、Improve 機能はプロンプト(extra_instructions)を調整しても「No code suggestions found for PR.」と回答されてしまいます。(コードのレビューを目的に開発されたツールのため、このような挙動になった可能性が考えられます) そこで、 Improve 機能の システムプロンプト をカスタマイズすることでレビューが可能かを検証したところ、生成AIからの回答が返ってきたため、システムプロンプト側もカスタマイズすることにしました。 システムプロンプト とは、LLM を Invoke する際に、ユーザープロンプトとは別に渡されるプロンプトのことを指します。アウトプットする項目やフォーマットの具体的な指示なども含みます。 先程ご説明した extra_instructions はシステムプロンプトの一部であり、PR-Agent ではユーザーからの追加指示が存在する場合、その指示がシステムプロンプトに追加で組み込まれる仕組みになっているようです。 # Improve のシステムプロンプト抜粋 [pr_code_suggestions_prompt] system="""You are PR-Reviewer, a language model that specializes in suggesting ways to improve for a Pull Request (PR) code. Your task is to provide meaningful and actionable code suggestions, to improve the new code presented in a PR diff. 〜省略〜 {%- if extra_instructions %} Extra instructions from the user, that should be taken into account with high priority: ====== {{ extra_instructions }} # ここに extra_instructions で指定した内容が追記される。 ====== {%- endif %} 〜省略〜 PR-Agent は extra_instructions と同様にシステムプロンプトを GitHub Actions から編集できることができます。 既存のシステムプロンプトをカスタマイズすることで、最終的にコードだけでなく文章もレビューできるようになりました。 以下、カスタマイズ例の一部をご紹介します。 まず、コードに特化した指示をテックブログをレビューできるように変更していきます。 カスタマイズ前のシステムプロンプト You are PR-Reviewer, a language model that specializes in suggesting ways to improve for a Pull Request (PR) code. Your task is to provide meaningful and actionable code suggestions, to improve the new code presented in a PR diff. # 日本語訳 # あなたは PR-Reviewer で、Pull Request (PR) のコードを改善する方法を提案することに特化した言語モデルです。 # あなたのタスクは、PR diffで提示された新しいコードを改善するために、有意義で実行可能なコード提案を提供することです。 カスタマイズ後のシステムプロンプト You are a reviewer for an IT company’s tech blog. Your role is to review the contents of .md files in terms of the following. Please check each item indicated as a check point of view and point out any problems. # 日本語訳 # あなたはIT企業の技術ブログのレビュアーです。 # あなたの役割は、.mdファイルの内容を以下の観点からレビューすることです。 # チェックポイントとして示されている各項目を確認し、問題があれば指摘してください。 次に、具体的な指示が記載されている部分をテックブログをレビューできるように変更していきます。 アウトプットに関する指示を変えてしまうとプログラム側にも影響してしまうため、あくまでコードのレビュー指示をテキストに置き換えてテックブログをレビューできるようにカスタマイズをしています。 カスタマイズ前のシステムプロンプト Specific instructions for generating code suggestions: - Provide up to {{ num_code_suggestions }} code suggestions. The suggestions should be diverse and insightful. - The suggestions should focus on ways to improve the new code in the PR, meaning focusing on lines from '__new hunk__' sections, starting with '+'. Use the '__old hunk__' sections to understand the context of the code changes. - Prioritize suggestions that address possible issues, major problems, and bugs in the PR code. - Don't suggest to add docstring, type hints, or comments, or to remove unused imports. - Suggestions should not repeat code already present in the '__new hunk__' sections. - Provide the exact line numbers range (inclusive) for each suggestion. Use the line numbers from the '__new hunk__' sections. - When quoting variables or names from the code, use backticks (`) instead of single quote ('). - Take into account that you are reviewing a PR code diff, and that the entire codebase is not available for you as context. Hence, avoid suggestions that might conflict with unseen parts of the codebase. カスタマイズ後のシステムプロンプト Specific instructions for generating text suggestions: - Provide up to {{ num_code_suggestions }} text suggestions. The suggestions should be diverse and insightful. - The suggestions should focus on ways to improve the new text in the PR, meaning focusing on lines from '__new hunk__' sections, starting with '+'. Use the '__old hunk__' sections to understand the context of the code changes. - Prioritize suggestions that address possible issues, major problems, and bugs in the PR text. - Don't suggest to add docstring, type hints, or comments, or to remove unused imports. - Suggestions should not repeat text already present in the '__new hunk__' sections. - Provide the exact line numbers range (inclusive) for each suggestion. Use the line numbers from the '__new hunk__' sections. - When quoting variables or names from the text, use backticks (`) instead of single quote ('). その後、先程ご説明した「Wikiでプロンプトを管理」の手順と同様に、新たにシステムプロンプト用の Wiki を追加します。 - name: Set up Wiki Info id: wiki_info run: | set_env_var_from_file() { local var_name=$1 local file_path=$2 local prompt=$(cat "$file_path") echo "${var_name}<<EOF" >> $GITHUB_ENV echo "prompt" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV } set_env_var_from_file "REVIEW_PROMPT" "./wiki/pr-agent-review-prompt.md" set_env_var_from_file "DESCRIBE_PROMPT" "./wiki/pr-agent-describe-prompt.md" set_env_var_from_file "IMPROVE_PROMPT" "./wiki/pr-agent-improve-prompt.md" + set_env_var_from_file "IMPROVE_SYSTEM_PROMPT" "./wiki/pr-agent-improve-system-prompt.md" - name: PR Agent action step 〜 省略 〜 + PR_CODE_SUGGESTIONS_PROMPT.system: | + ${{env.IMPROVE_SYSTEM_PROMPT}} 以上の手順でカスタマイズすることで、通常はコードレビューに特化した PR-Agent の Improve 機能をブログ記事のレビューにも対応させることができました。 注意点として、システムプロンプトを変更しても必ずしも 100% 期待通りの回答が返ってくるわけではありません。これは、プログラムコードに対して Improve 機能を使用した場合も同様です。 PR-Agent を導入した結果 PR-Agent を導入することで、以下のような点でメリットがありました。 レビュー精度の向上 普段見落としがちな内容も指摘してくれるので、コードレビューの精度が向上しました クローズされた過去の PR もレビューできるため、過去のコードを見直すことができます 過去の PR に対してレビューを行うことで、継続的な品質向上やコードベースの改善にもつながります プルリクエスト(PR)作成の負荷軽減 プルリクエストの要約機能により、プルリクエストの作成負担が軽減しました 要約された内容をレビュアーが確認することで、レビューの効率が向上し、マージまでの時間が短縮しました エンジニアスキルの向上 技術の進歩は非常に素早く、普段の業務をしつつキャッチアップし続けることは難しいものです AI によって提供された指摘はベストプラクティスを学ぶのに非常に効果的でした テックブログのレビュー テックブログにPR-Agentを導入することで、レビューの負荷が軽減されました。完璧ではないものの記事の誤字脱字や文法のチェックや内容の一貫性や論理の整合性についても指摘してくれるので、見落としがちなミスも発見できます 以下に、実際のテックブログ( イベントレポート DBRE Summit 2023 )をレビューをした例となります。 ![pr_agent_describe.png](/assets/blog/authors/mhoshino/pr_agent_describe_blog.png =800x) PR-Agentによるテックブログのプルリクエスト(PR)要約(Describe) ![pr_agent_describe.png](/assets/blog/authors/mhoshino/pr_agent_review_blog_01.png =800x) ![pr_agent_describe.png](/assets/blog/authors/mhoshino/pr_agent_review_blog_02.png =800x) PR-Agentによるテックブログのプルリクエスト(PR)レビュー(Review) ![pr_agent_describe.png](/assets/blog/authors/mhoshino/pr_agent_improve_blog.png =800x) PR-Agentによるテックブログの変更案(Improve) また、注意点として以下の点から最終的な判断は人間が行うことが重要です。 全く同じプルリクエスト(PR)に対して PR-Agent が行うレビュー結果が毎回異なり、回答精度のばらつきがある PR-Agent によるレビューが、関連性が低いまたは完全に見当違いなフィードバックを生成する場合がある まとめ 本記事では、PR-Agent の導入とカスタマイズがどのように作業効率を向上させたかについてご紹介しました。完全なレビュー自動化はまだ実現できませんが、設定とカスタマイズにより、補助的な役割として開発チームの生産性向上に貢献しています。 今後もこの PR-Agent を活用して、さらなる効率化と生産性の向上を目指していきたいと思います。
アバター
Introduction My name is Kang and I am in charge of front-end development of the KINTO ONE New Vehicle subscription system at KINTO Technologies. Allow me to briefly introduce you the project I was assigned to. The KINTO ONE New Vehicle Subscription System is gradually incorporating the application of new architecture. The front-end team uses Next.js, TypeScript, and Atomic Design as the design pattern. In this article, we will introduce "Atomic Design", a methodology we are applying in our projects. What is Atomic Design? The Definition of Atomic Design Atomic Design is a UI design methodology created by Brad Frost, which provides a framework for developing UI designs by breaking them down into components. To maximize code reusability, the idea is to define which would be your smallest building blocks (“Atoms”) and create higher-level components based on them. In recent years, there have been an increase in the number of cases where JavaScript is used for front-end web development, with Vue and React serving as frameworks and libraries. Vue and React are known for their component-based development approach. As a result, Atomic Design, which emphasizes designing systems in a component-centric way, is gaining even more attention. The benefits of Atomic Design Atomic Design facilitates the creation of a system that enhances component reusability by dividing them into stages: Increases component reusability. Components can be develop and tested separately from applications. (Separate libraries such as Storybook or Jest allow you to check and test each component.) CSS is tightly bound to specific components, making it easier to manage CSS. Reusing existing components ensures a consistent design. The disadvantages of Atomic Design The need to design in order to enhance component reusability could create complexity in some cases due to pre-page element verification and the proliferation of configuration components: It becomes difficult to proceed without designing highly reusable components. Modifying components frequently can become complex and difficult to maintain. Experiments and discoveries A component structure for front-end development Separating Presentational and Container Components the Presentational component is responsible for configuring the appearance of the screen, and the Container component is responsible for executing API calls and front-side logic. const MypageContainer: React.FC<Props> = ({ setErrorRequest }) => { const { statusForCancellation, cancellationOfferDate, callGetCancellationStatus } = useCancellationStatus(); const { authorizationInfo, memberType } = useAuth(); useEffect(() => { ... }, []); useEffect(() => { ... }, [currentItem]); const initSet = async () => { try { // Data settings required for API calls or rendering ... } finally { ... } }; const onChangeCurrentItem = (currentSlideNumber: number) => { // Event logic processing ... }; /** * Render judgment is made with passed Props, set state and flag, etc. * Render the component that follows the judgment. * Each component is assembled into a Presentational component */ return isLoading ? ( <Loading /> ) : ( <div className="p-mypage"> <div className="l-container"> <div className="l-content--center"> <MypageEstimateInfo data={estimateInfo} getEstimateInfo={getEstimateInfo} setErrorRequest={setErrorRequest} /> </div> <div className="l-content--center"> <MypageContactContent data={entryInfo} memberType={memberType} /> </div> <ErrorModal isOpen={errorDialogRequest.isOpen} errorDialogRequest={errorDialogRequest.error} onClose={() => setErrorDialogRequest({ ...errorDialogRequest, isOpen: false })} /> </div> </div> ); }; export default MypageContainer; const MypageContactContent: React.FC<Props> = ({ data, isContactForm = true, memberType }) => { return ( <> <div className="o-contactContent"> <ContentsHeadLine label="inquiries" /> // --> atom {isContactForm && <ContactAddressWithFormLink />} // --> molecules <TypographyH4> Enquire by phone </TypographyH4> // --> atom <ContactAddressWithForAccident // --> molecules tel={CONTACT_ADDRESS_TEL.member} isShowForAccident={memberType === MEMBER_TYPE.MEMBER} /> </div> </> ); }; export { MypageContactContent }; You can see that the container component above determines the configuration of the component based on the value obtained from the API and the value set after logic processing. The rendered component then builds the presentational component to display the screen via the value passed from container to Props . Component Group Configuration (Atoms, Molecules, Organisms, Template, Pages) Atom Atom is the most basic and indivisible component. Atoms can be combined to create bigger units for their usage, such as Molecules and Organisms. 2.Molecules Molecules are the combination of multiple atoms and have their particular characteristics. The important thing about a Molecule is that it will only serve one purpose. Organisms Organisms are more complex than the previous component hierarchy, having clear areas where they will appear in a service, with their specific context. Compared to Atoms or Molecules, due to its context, it will have less reusability as it is more specific. Template Template pages can be created by combining multiple Organisms and Molecules. They are essentially wireframes in which the actual components are placed and structured in as layout. Pages Pages are where the content that users can see is populated. You could call it the instances of Templates. Synergy with Storybook Storybook is an open source UI tool. With Storybook, you can quickly visualize the UI components you are building. Integration with the Storybook library makes UI management easier (UI testing will become easier to perform too). Summary Today I shared my experiences when implementing Atomic Design in my project. While some concepts were initially unclear when applying these principles in practice, we adjusted the scope and classification of component groupings to better fit our project (for example, components that were expected to be out-of-scope of Organism were managed in a separate unit we called Features.) Without a clear definition from the start, components may need to be redesigned, recreated, or reclassified mid-process, requiring careful attention (or adding more component hierarchies could be another option). I also found that collaboration and communication between the design and development teams were very important (because designs needs to be broken down into components such as Atom, Molecule, or Organism.) The alignment of understanding regarding the criteria for each component grouping is essential. It will be essential for all sides to have alignment meetings together in order to ensure that everyone is on the same page, as each team typically concentrates on their individual roles. Atomic Design has its own set of pros and cons, but I believe that if the entire team understands it and defines it well before implementation, it will be easier later to create a frontend development environment that facilitates smooth collaboration and maintenance. Thank you for reading. Reference atomic-web-design Brad Frost design systems are for user interfaces
アバター
はじめに こんにちは!KTCでAndroidエンジニアをしている 長谷川 です! 普段はmy routeというアプリの開発をしています。my routeのAndroidチームのメンバーが書いた他の記事も是非読んで見てください! Android開発をする時に知っておかないとバグを引き起こしそうな「地域別の設定」について SwiftUI in Compose Multiplatform of KMP 本記事ではKotlin(Android)でOG情報を取得する方法と、その過程で文字コードの扱いに困った話を紹介します。 この記事で解説すること OGPとは KotlinでOGPを取得する方法 OGPで取得した情報が文字化けする原因 文字化けの対応方法 OGPとは OGPとは「Open Graph Protocol」の略で、Webページなどを他のサービスにシェアしたときに、Webページのタイトルやイメージ画像を正しく伝えるためのHTML要素です。 OGPが設定されているWebページはこれらの情報を表すmetaタグが存在します。以下はその中の一部を抜粋したmetaタグです。OG情報を取得したいサービスはこれらのmetaタグから情報を読み込むことができます。 <meta property="og:title" content="ページのタイトル" /> <meta property="og:description" content="ページの説明文" /> <meta property="og:image" content="サムネイル画像のURL" /> KotlinでOGPを取得する方法 今回は通信のためにOkHttp、HTTPのパースのためにJsoupを使用します。 まずはOkHttpを使って、OG情報を取得したいURLのWebページにアクセスします。エラーハンドリングは要件によって変わりますので省略します。 val client = OkHttpClient.Builder().build() val request = Request.Builder().apply { url("OG情報を取得したいURL") }.build() client.newCall(request).enqueue( object : okhttp3.Callback { override fun onFailure(call: okhttp3.Call, e: java.io.IOException) {} override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) { parseOgTag(response.body) } }, ) 次にJsoupを使って中身をパースします。 private fun parseOgTag(body: ResponseBody?): Map<String, String> { val html = body?.string() ?: "" val doc = Jsoup.parse(html) val ogTags = mutableMapOf<String, String>() val metaTags = doc.select("meta[property^=og:]") for (tag in metaTags) { val property = tag.attr("property") val content = tag.attr("content") val matchResult = Regex("og:(.*)").find(property) val ogType = matchResult?.groupValues?.getOrNull(1) if (ogType != null && !content.isNullOrBlank()) { ogTags[ogType] = content } } return ogTags } これでogTagsに必要なOG情報が入りました。 OGPで取得した情報が文字化けする原因 ここまでで大抵のWebページのOG情報は正しく取得できると思います。しかし一部のWebページの場合、文字化けが発生してしまう可能性があります。ここではその原因を解説します。 今回は下記のように string() という関数を呼びました。 val html = response.body?.string() ?: "" この関数は以下の優先順位で文字コードを選択します。 BOM(Byte Order Mark)の情報 レスポンスヘッダーのcharset 1,2に指定がなければUTF-8 詳しくは OkHttpのリポジトリのコメント に記載があります。 はい、つまりBOMの情報がなくて、レスポンスヘッダーのcharsetの指定がなくて、Shift_JISなどUTF-8以外でエンコードされているWebページがあったらどうなると思いますか? ... 文字化けが発生します。なぜならデフォルトのUTF-8でデコードしてしまうからです。 さて、どうしましょうか?次のセクションでは具体的な対応方法を解説します。 文字化けの対応方法 前のセクションで文字化けしてしまう原因が分かりました。実はWebページにおいて文字コードは下記のようにHTML内にも指定されている可能性があります。BOMの情報もなくて、レスポンスヘッダーのcharsetも指定されていない場合はこの情報を使用するしかありません。 <meta charset="UTF-8"> <!-- HTML5 --> <meta http-equiv="content-type" content="text/html; charset=Shift_JIS"> <!-- HTML5より前 --> しかし上記の文字コードが指定されたmetaタグを読み込むために、HTMLを文字コードに応じてパースする必要があるという矛盾が発生します。 と一瞬思いますが、例えばUTF-8やShift_JISはASCII文字の範囲では互換性があるため、一旦UTF-8でデコードしても問題ありません。 (この方法だとパースを2回行うことがあります。もしmetaタグのバイト配列をあらかじめ調べておけばパースする前に文字コードを判定することもできるかもしれませんが、今回はコードの分かりやすさを重視しました。) というわけで下記のようなコードを書くことができます。 /** * レスポンスボディからJsoupのDocumentを取得する * レスポンスボディのcharsetがUTF-8以外の場合は、charsetを取得して再度パースする */ private fun getDocument(body: ResponseBody?): Document { val byte = body?.bytes() ?: byteArrayOf() // ResponseHeaderにcharsetが指定されている場合、そのcharsetでデコードする val headerCharset = body?.contentType()?.charset() val html = String(byte, headerCharset ?: Charsets.UTF_8) val doc = Jsoup.parse(html) // headerCharsetが指定されている場合、そのcharsetで正しくパースできているはずなので // そのままreturnします。 if (headerCharset != null) { return doc } // HTML内のmetaタグからcharsetを取得します。 // このcharsetがない場合は、文字コードが不明なので、UTF-8でパースされたdocを返します。 val charsetName = extractCharsetFromMetaTag(html) ?: return doc val metaCharset = try { Charset.forName(charsetName) } catch (e: IllegalCharsetNameException) { Timber.w(e) return doc } // metaタグで指定されたcharsetとUTF-8が異なる場合、metaタグで指定されたcharsetで再度パースする // パースは比較的重たい処理なので、二重で行わないようにします。 return if (metaCharset != Charsets.UTF_8) { Jsoup.parse(String(byte, metaCharset)) } else { doc } } /** * HTMLのmetaタグからcharsetの文字列を取得する * * HTTP5未満 → meta[http-equiv=content-type] * HTTP5以上 → meta[charset] * * @return charsetの文字列 ex) "UTF-8", "SHIFT_JIS" * @return charsetが見つからない場合はnull */ private fun extractCharsetFromMetaTag(html: String): String? { val doc = Jsoup.parse(html) val metaTags = doc.select("meta[http-equiv=content-type], meta[charset]") for (metaTag in metaTags) { if (metaTag.hasAttr("charset")) { return metaTag.attr("charset") } val content = metaTag.attr("content") if (content.contains("charset=")) { return content.substringAfter("charset=").split(";")[0].trim() } } return null } その後JsoupのDocumentを作成する関数を、今作成した処理を使って以下のように変更しましょう。 - val html = body?.string() ?: "" - val doc = Jsoup.parse(html) + val doc = getDocument(body) おわりに お疲れ様でした。 大抵のWebページの文字コードはUTF-8ですし、仮に異なる文字コードを使用しているとしてもBOMやレスポンスヘッダーにcharsetが指定されていることがほとんどです。したがって今回のような問題が発生することはあまりないと思います。 しかし、仮にそのようなサイトを発見してしまった場合、原因の把握や修正方法が難しい場合があります。 本記事がどなたかの助けになれば幸いです。
アバター
Introduction Hello, Morino from KINTO Technologies CSIRT here. I participated in the Japan Ceasert Association's TRANSITS Workshop in Summer 2023, which ran for three days from July 12 2023 (Wed) to 14 (Fri). TRANSITS provides training content from Europe on the establishment and operation of CSIRT. In this workshop, I learned about the four modules: Organization, Operations, Technology and Law. CSIRT stands for Computer Security Incident Response Team, referring to a team that responds to computer security incidents. Computer security incidents include leakage of confidential information, unauthorized intrusion into computer systems, and malware infections, etc. Organization Module In the Organizational Module, I learned about the role of the CSIRT, the services it provides, and the structure, etc. of its team. There was also an incident scenario exercise in which each team played the roles of a CSIRT member, an attacker, etc. In this exercise, I experienced the flow of incident response and the importance of communication. Operations Module In the Operations module, I learned about incident response and incident handling. "Incident response" refers to addressing incidents such as analysis and containment of incidents, etc. whereas "incident handling" refers to the overall response to incidents. There was also an exercise for each team to examine the incident handling process. This exercise taught me about the importance of preparing an incident response procedure in advance. Technology Module In the technical module, I learned about the attackers' techniques and methods, etc. During the lecture, there was a talk from a security vendor who is involved in the analysis of incidents that occur in various organizations. In almost every incident in which the security vendor was involved in, they claimed that the attacks could have been detected if properly monitored. I was also impressed by the following words, which were described as the foundation of security. Close doors after you have opened them Tidy up after you If you start a system, always put maintenance measures in place Law module In the law module, I learned about cybersecurity laws and regulations. The legal requirements and precautions for capturing and storing logs were specifically discussed in detail. Along with an introduction to the eDiscovery system, a security service provider also explained how to cooperate with the police. Summary The TRANSITS Workshop in Summer 2023 was an unforgettable experience. I was able to deepen my knowledge and skills related to CSIRT through the lectures I attended. Furthermore, participating in exercises allowed me to interact with fellow participants, enriching the overall experience. I highly recommend this workshop for those who are establishing or operating a CSIRT.
アバター
こんにちは、人事グループのHOKAです。 (過去には 人事採用グループをつくろう ~3年で社員数300人に急成長した組織の裏側~ という記事も書いていますのでそちらもぜひご覧ください) 2023年度も残り3日という2024年3月28日。 大阪、名古屋、東京で働くKINTOテクノロジーズ開発支援部メンバー40人が東京渋谷のGoogleオフィスに集まり、10X Innovation Culture Program を実施したのでそのレポートをしてみます。 10X Innovation Culture Program とは Google 日本法人Japanが2023年9月に公開 した「イノベーションを生み出す組織環境づくりのためのリーダーシップ・プログラム」です。 内容としてはオンライントレーニング、アセスメントツール、ソリューションパッケージの3つの要素で構成されており、 オンライントレーニングで「Think 10X」の概要を知り アセスメントツールで自分たちの立ち位置を知り、課題を把握し ソリューションパッケージで課題解決に役立てる と自然な流れで組織変革に役立つ考え方や知見を自分たちの組織に取り入れることができるプログラムとなっています。 きっかけ 弊社DBREの あわっち はJagu'e'rにある分科会の一つである「 企業カルチャーとイノベーションを考える分科会 」の運営メンバーの一人として活動しており、10X Innovation Culture Programにも強い関心を持っていました。 このプログラムが公開されたことをきっかけに、あわっちが社内の有志を集ってGoogleオフィスでこのプログラムをライトに体験してみるという企画をしていたのでそこに私が参加したのがことのはじまりです。 あまりにも楽しかったし学びが多かったので、翌日の朝会で共有したら、「それ、もっと全体でやろうよ!」というマネージャー、さらにそれを聞いた部長の「開発支援部だけなら私の判断で開催できるでしょ!」というテンションで、あれよあれよという間に開催が決定しました。 社長や副社長に承認を貰う前から「やる」ということだけは決まっているこの社風、このスピード感、嫌いじゃないです。 ここからどうやって開発支援部全体という40名を超える大きな規模で実施するか、試行錯誤が始まりました(笑) 実施までの道のり はじめは弊社メンバーだけで実施しようと考えていたのですが、それだとどうしても開発支援部以外の部署に展開することが難しくなってしまいます。 「10X Innovation Culture」を実現するためには自分たちがそれをしっかりと理解して、そして自信を持って語れる様にならなければなりません。 そこで、今回は実際のプログラムの進め方を学び、そしてこれを今後社内に展開するために必要な「ファシリテーター」を育てることを前提とし、GoogleオフィスでGoogle社員による10X Culture Programを実施することにしました。 開発支援部内で事前にファシリテーター希望者アンケートを行ったところ、人事以外のメンバーも職種、男女問わず17人のメンバーが「ファシリテーター希望」と答えてくれました。(希望者は、ファシリテーターになるつもりで10X Culture Program に参加してもらいました。) 全体の座組が決まったところでGoogleの2名、あわっち、HOKAが主となってコンテンツの企画をスタート。2023年10月に受講した体験を活かし、よりディスカッションに集中できるよう、「動画を見ておく、アセスメント回答を実施しておく」を事前に行うことにしました。 事前準備会 at オンライン 6つの動画を見る アセスメント回答をする 10X innovation culture program at Google office アセスメント結果から開発支援部の傾向を読み解く ディスカッションを2つ実施する 事前準備会やってみた (3月20日) あわっちも初めての事前準備会の準備を始めました。ところが、 提供されたアセスメントツールが期待通りに動かない! あわっちが力技で解決 英語対応済のアセスメントがない! 英語翻訳を社内のスペシャリストに依頼 英語の動画がない! YouTubeの翻訳ツールを活用 などなど、色んな課題が発生し、社内外の方に助けていただきました。 KINTOテクノロジーズの社員のうち、24%は外国籍です。改めて、英語対応の重要性を感じたシーンでした。 事前準備会では、あわっちがファシリテーターを担当しました。 10X Innovation Culture Program の動画 を1つずつ見る→アセスメントに回答するを繰り返していきました。 事前準備会の終わりには、アセスメントツールであるLooker Studioを介してその場で結果を共有しました。開発支援部全体の傾向や、グループごとの傾向が分かり、参加者はより前のめりになっていただけました。 ![アセスメント結果](/assets/blog/authors/hoka/20240611/assessment_result.png =750x) 当日 (3月28日) ついに3/28当日。東京、大阪、名古屋から総勢40名がGoogleオフィスに集結。普段なかなか入れないGoogleオフィスでの開催、気分は完全にお上りさん状態です(笑) ![Googleオフィスに集合](/assets/blog/authors/hoka/20240611/arriving.png =750x) 当日ファシリテーションをしてくださったのはGoogleのRikaさんとKotaさん。 オープニングではそもそも10X Innovation Culture Programにある「10X」とはなんなのか、Googleの事例を交えながら説明いただきました。 ![実施風景](/assets/blog/authors/hoka/20240611/state.png =750x) みんな真剣に聞き、メモをとっていますね。 そしていよいよディスカッションがスタート。このディスカッションでは限られた時間を最大限活かすため、事前準備のアセスメント結果の中で特に伸び代のあった「内発的動機づけ」と「リスクテイク」について5人程度のグループに分かれてそれぞれに対してディスカッションをしました。 「内発的動機づけ」では、「日々の業務にパッションを持って取り組むためには何が必要か」「社内でそれをどう実現できるか」などの視点でディスカッションを行いました。 一方の「リスクテイク」では、「新しいことにチャレンジする際の心理的ハードルを下げるには」「失敗を許容する風土をつくるにはどうすべきか」などについて意見を出し合いました。 ここで各グループをリードするのがファシリテーターの役割です。全員が適度に話せる様に、個別の内容だけにフォーカスしすぎない様に適切に発散させる様に、グループを回すことがこのカルチャーセッションのディスカッションでは求められます。 Googleから全体に提示されたワークショップを行うにあたってのAgreementは下記の内容でした。 大前提 学習のための機会と捉える 間違えて当たり前と割り切る 注意事項 自分の言葉が周りに与える影響を意識する 出された意見は善意に基づくものと解釈する 他の人が発言した内容を口外しない Let's Enjoy Google Culture! ニックネームで呼び合いましょう これらは実際にグループワークを円滑に活発に行うために非常に重要なポイントでした。 実際のディスカッションの様子はこちら ![ディスカッション1](/assets/blog/authors/hoka/20240611/discussion1.png =750x) ![ディスカッション2](/assets/blog/authors/hoka/20240611/discussion2.png =750x) ![ディスカッション3](/assets/blog/authors/hoka/20240611/discussion3.png =750x) ![ディスカッション4](/assets/blog/authors/hoka/20240611/discussion4.png =750x) ![ディスカッション5](/assets/blog/authors/hoka/20240611/discussion5.png =750x) ![ディスカッション6](/assets/blog/authors/hoka/20240611/discussion6.png =750x) 会を通じて終始非常に盛り上がり、同時に自分たちの課題、そしてそれらを改善するためにどの様なことができるのか、を学び、感じることができました。 実際のアンケート結果を共有します。 ![アンケート結果1](/assets/blog/authors/hoka/20240611/survey1.png =750x) ![アンケート結果2](/assets/blog/authors/hoka/20240611/survey2.png =750x) ![アンケート結果3](/assets/blog/authors/hoka/20240611/survey3.png =750x) Googleのお二人より嬉しいコメントもいただきました! Rikaさん お疲れ様でした!この盛り上がりは、このスペースに入られている皆様による事前準備があったからこそなので、改めてこちらからも御礼を伝えさせてください!ワークショップでの意欲的なご参加者様から、我々も元気をいただきました💪貴社のカルチャー変革を進めていかれる姿は、他企業様にも影響を与えると思います! Kotaさん こちらこそ貴重な機会を頂きありがとうございました!皆様の熱量に圧倒されました。Kinto様のカルチャー発展が次のステージに進むきっかけになれば良いなと思います。応援しています!我々もここから更に色々な取り組みに発展していけると良いなと思っていますのでよろしくお願いいたします😃 後日談 ちょうど3月末、ということもありKINTOテクノロジーズでは目標設定の時期と重なっていました。このプログラムを受けたことで方々から「 それって10Xじゃないよね? 」という声が聞こえてきて、自分たちの組織がどうありたいか、一人一人がこれまで以上に真剣に考える様になったことを実感しています。 また、Googleで実践していることで有名な「20%ルール」をまずは開発支援部で導入してみようということにもなりました。これまで「Googleだからできるんだよね」というイメージがあることでなかなか踏み出せなかったこの制度も、実際に自分たちでこのプログラムを体験したことで「自分たちでもできるかも」という様にマインドの変化が生まれました。 そして、3か月後の6月末(もうすぐですね)にも開発支援部で開催が決定。今度は自分たちで運営します。 また、開発支援部以外の部門でも導入すべく、現在準備を進めています。 ファシリテーターとしての活動が期待されます。 いかがでしたでしょうか? 企業カルチャーに課題を感じている、もっと会社をよくしたい、そう感じた方はぜひ一度 10X Innovation Culture Program を見てみてください。きっと良い気付きを得ることができると思います。 お知らせ Google Cloud Next Tokyo '24 登壇決定👏👏 2024年8月2日、** Google Cloud Next Tokyo '24 ** で 10X Innovation Culture Program 体験ワークショップの事例紹介として弊社開発支援部部長の岸と 10X を社内で推進しているあわっちが登壇します。 私たちがこの体験を通じて感じたことをありのままにお話ししますのでお時間がある方はぜひお立ち寄りください。
アバター
Introduction Hello! My name is Ren.M from KINTO Technologies' Project Promotion Group. My main role is to develop the front-end of KINTO ONE (Used Vehicles) . This time, we'd like to introduce the basics of TypeScript, specifically the type definitions. Target audience of this article Those who want to learn about TypeScript type definitions Those who want to learn TypeScript following their JavaScript studies What is TypeScript? TypeScript is a language that operates as an extension of JavaScript, so both TypeScript and JavaScript use the same syntax. In traditional JavaScript, there's no obligation to specify data types, allowing for more flexible coding. However, improving program reliability and mitigate issues such as inconsistent typing was required so we sought a solution. Enter TypeScript, leveraging static typing to address these concerns. By understanding type definitions you will be able to code smoothly and ensure safer data transfer. How TypeScript differs from JavaScript JavaScript allows for different assignments of the following data types: let value = 1; value = "Hello"; However, in TypeScript, the behavior is as follows: let value = 1; // It is not a number type so it cannot be assigned value = "Hello"; // Can be assigned because it is the same number type value = 2; The main type of data // string type const name: string = "Taro"; // number type const age: number = 1; // boolean type const flg: boolean = true; // array string type const array: string[] = ["apple", "banana", "grape"]; Explicitly defining a type after : is called a type annotation. Type inference As mentioned above, TypeScript automatically assigns types without using type annotations. This is called type inference. let name = "Taro"; // string type // Bad: name cannot be assigned as it is a type of string name = 1; // Good: Can be assigned because it is a type of string name = "Ken"; Array definition type // an array that accepts only a number type const arrayA: number[] = [1, 2, 3]; // an array that accepts only number or string types const arrayB: (number | string)[] = [1, 2, "Foobar"]; interface The type definition of an object can be an interface . interface PROFILE { name: string; age?: number; } const personA: PROFILE = { name: "Taro", age: 22 }; Similar to the age mentioned above, with a "?" following the key elementyou can also make the property arbitrary through a grant. // 'age' element is not required const personB: PROFILE = { name: "Kenji", }; Intersection Types The concatenation of multiple types is called Intersection Types. The following applies to STAFF . type PROFILE = { name: string; age: number; }; type JOB = { office: string; category: string; }; type STAFF = PROFILE & JOB; const personA: STAFF = { name: "Jiro", age: 29 office: "Tokyo", category: "Engineer", }; Union Types More than one type can be defined using | (pipe). let value: string | null = "text"; // Good value = "kinto"; // Good value = null; // Bad value = 1; In the event of arrays let arrayUni: (number | null)[]; // Good arrayUni = [1, 2, null]; // Bad arrayUni = [1, 2, "kinto"]; Literal Types Assignable values can also be explicitly typed. let fruits: "apple" | "banana" | "grape"; // Good fruits = "apple"; // Bad fruits = "melon"; typeof If you want to inherit a type from a declared variable, use typeof. let message: string = "Hello"; // inherits the string type from the message let newMessage: typeof message = "Hello World"; // Bad newMessage = 1; keyof keyof is used to create a type from the property name (key) from the type of an object. type KEYS = { first: string; second: string; }; let value: keyof KEYS; // Good value = "first"; value = "second"; // Bad value = "third"; enum Enum (enumeration type) is a function that automatically assigns a consecutive number. The following assigns 0 to SOCCER and 1 to BASEBALL . enum improves readability and makes maintenance easier. enum SPORTS { SOCCER, BASEBALL, } interface STUDENT { name: string; club: SPORTS; } // 1 is assigned to club const studentA: STUDENT = { name: "Ken", club: SPORTS.BASEBALL, }; Generics By using Generics, you can declare a type each time you use it. This is useful for situations where you want to repeat the same code in different types. Conventionally, T and similar syntax are often used. interface GEN<T> { msg: T; } // declare the type of T when used const genA: GEN<string> = { msg: "Hello" }; const genB: GEN<number> = { msg: 2 }; // Bad const genC: GEN<number> = { msg: "message" }; When you define a default type, declarations such as <string> are optional. interface GEN<T = string> { msg: T; } const genA: GEN = { msg: "Hello" }; You can also use extends together to restrict the types that can be used. interface GEN<T extends string | number> { msg: T; } // Good const genA: GEN<string> = { msg: "Hello" }; // Good const genB: GEN<number> = { msg: 2 }; // Bad const genC: GEN<boolean> = { msg: true }: For use with functions function func<T>(value: T) { return value; } func<string>("Hello"); // <number> is not required func(1); // multiple types are allowed func<string | null>(null); In the event that extends is used in a function function func<T extends string>(value: T) { return value; } // Good func<string>("Hello"); // Bad func<number>(123); When used in conjunction with the interface interface Props { name: string; } function func<T extends Props>(value: T) { return value; } // Good func({ name: "Taro" }); // Bad func({ name: 123 }); Conclusion In this article, we covered the fundamentals of TypeScript. What are your thoughts? TypeScript's usage in front-end development has been on the rise, and I believe that learning and using TypeScript can prevent data type inconsistencies and facilitate safer development with fewer bugs. I hope this article was helpful! There are many other articles on the tech blog, so please check them out!
アバター
Introduction I am Kanaya, a member of the team[^1] that develops payment platforms used by multiple services at KINTO Technologies. Today I will be introducing a case of remote mob programming for a new project, highlighting its role in achieving timely development. [^1]: For other initiatives of payment platforms, please see Domain-Driven Design (DDD) incorporated in a payment platform intended to allow global expansion . Background We started a project to create a new internal payment system. The project team included a product owner located in Tokyo and three software developers: one based in Tokyo and two in Osaka, myself included. In order to aim for quick development and cost efficiency, especially AWS costs, we opted for React powered SPA based on AWS Amplify for the frontend, while using the AWS Serverless Application Model to create essential APIs. Challenges At the beginning of the project, I felt a bit concerned , especially about using AWS services that were new to me. When I discussed my concerns with each team member, I found out that the three of us, who had gathered together, had different areas of technical expertise. Specifically, when divided into three areas frontend, backend, and infrastructure (AWS), we found out that we were good at two areas but lacked confidence in one. For example, in the case of K-san and myself, we're proficient in frontend and backend development, but lack confidence in AWS. Thankfully, we were reassured by the fact that both of us brought significant technical expertise to the table, which meant the workload wouldn't fall solely on one person. Team Member Frontend Backend Infrastructure (AWS) K-san 😊 😊 😐 T-san 😊 😐 😊 N-san 😐 😊 😊 Chart displaying skill areas and the corresponding areas of expertise of the development team members Also, the biggest concern I learned during our conversations was that there was the lack of common understanding when developing as a team. For example, with the frontend alone, what should be the granularity of the components? At which level of detail should state management be performed? Should we go with a promise-based approach or with async/await? How much testing should we write? Since there was no single line of code at the beginning, every single decision had to be made from scratch. Our team consisted of members who had joined the company less than a year ago, so we lacked having common points of reference to draw upon. So we thought that building a common understanding had to come first. To sum it up, we identified the following two major challenges to the success of the project: Given the lack of a shared understanding at the project's beginning, our priority was to establish common ground (to minimize future misunderstandings and ensure the creation of a high-quality product). The need to raise the base level of our technical capabilities by sharing and complementing each other's areas of expertise. In order to solve the above two issues, we decided to adopt mob programming as a development approach for this project. What is Mob Programming? Mob programming (which I will referred to as "MobPro" from now on) is described in the book Mob Programming Best Practices as "three or more people sitting in front of one computer working together to solve a problem." Everyone participates using one PC (screen), but there are actually two roles. Since it involves three or more participants, decisions and instructions are reached through discussions with at least two of the team members or ‘mobs’, while a designated 'typist' apply their instructions into code. Typist (operates the PC and writes code) Mob (discusses and directs development direction) I think the concept of resource efficiency and flow efficiency are important in understanding MobPro where three or more people are involved in a single task. You can find more details on Flow Efficiency and Resource Efficiency #xpjug - SlideShare (in Japanese), but MobPro is a way of working that fully focuses on workflow efficiency. While our primary focus isn't only about maximizing workflow efficiency, our work style naturally centers around it as long as we continue to use MobPro. Trial and Error/ Measures Taken Due to geographical constraints, we conducted mob programming remotely via Zoom. We knew it was necessary to solve the issues in order to make the project a success, so we incorporated MobPro with the following measures. Increase the amount of information To increase the amount of information, we worked on the screen while sharing the typist's entire desktop. The shared window alone does not tell us what is happening outside the window. The reason for sharing the entire desktop was to share the use of the OS and tools as well. Next, the typist made an effort to vocalize thoughts while working as much as possible. The intention is also to check for any discrepancies, especially since only the typist can actually work on the output. I noticed that the typist was often included in the discussions. Although different from the original MobPro typist's role, building a common understanding is important, so we made valid the typist's participation in discussions! Time management Since we knew that it would be difficult for more than three people to get together spontaneously for MobPro, we tried to schedule dedicated times in our calendars. Scheduling MobPro sessions in advance helped establish a coding day´s rhythm more effectively. We also added the Zoom Timer app to set a time limit. Since MobPro involves focused work, it is easy to become fatigued if continued without breaks. To include adequate shifts and breaks, we used a timer for time management. According to books and case studies of other companies[^2], it seemed that rotation times are very short, with 10-minute shifts, but we decided on 30 minutes partly due to the following task unit separation. [^2]: https://techblog.yahoo.co.jp/entry/2020052730002064/ Further subdivide feature tickets and leave as TODO comments For the features subject to MobPro, tasks were subdivided in advance and left as TODO comments in the core source code to be modified. Leaving TODO comments had two benefits. The first one is that it helps refine the goal of the feature. The second, that it creates good opportunities to create closures and wrap up tasks, allowing the MobPro role to be smoothly passed on to the next person in charge. For example, when creating a new API to update user information for payment processing, the following TODO comments were written before writing the code. At this point, the work content and work order were clear, allowing us to proceed smoothly with role rotation and breaks. # TODO : add definition of user information update to openapi # TODO : infrastructure - create an interface to update user information # TODO : infrastructure - process implementation of user information update # TODO : application - validation process implementation # TODO : application - process implementation of user information update # TODO : add lambda definition to sam template # TODO : deployment and operation check def lambda_handler(event, context): pass A relative criterion to determine when to use MobPro? Are we going to develop everything with MobPro? This question arose at an early stage, but we decided to prioritize features that seem difficult or required discussion to be implemented by MobPro. While relative difficulty among the features to be tackled serves as one way to select features for mob programming during the sprint, we also found that our shared awareness of what we considered difficult was well-aligned and it made sense to everyone to proceed this way. We also balanced our time coding the simpler features outside of our MobPro schedule. Since the difficult features were taken care of with MobPro, we experienced a surge in pull requests for simple features once MobPro was finished. Applying MobPro to tasks other than programming The term "mob programming" does not imply that it must be used only for that purpose. For this reason, we made the decision to utilize it for purposes beyond programming. Here are two examples: In code reviews Code reviews were also sometimes done with MobPro. Since we didn't do everything with MobPro, there was inevitably code not seen by everyone in the development process. At the beginning of each MobPro session, we set aside some time to explain the code created outside of MobPro, allowing everyone to listen and ask questions. Thanks to this dedicated time, we were able to quickly go through the review process, which often becomes the bottleneck. Tasks related to operation We also used our MobPro time for operational tasks. Specifically, when setting up the Cognito user pool and configuring GitHub Actions settings for the first time, we made use of our MobPro time to work on it collaboratively, with everyone watching. This also allowed us to get an idea of the operational aspects of the project. Ensuring that all team members communicate with the product owner Although we now have a common understanding within the development team, excluding the product owner poses a risk of misalignment, and indeed, some misunderstandings did occur. In the beginning, the product owner and I had a lot of conversations, yet the decision-making process remained vague without keeping particular minutes, leading to a gap in our understanding. As a countermeasure, we made sure that everyone participated in communication with the product owner, and that minutes were kept in real time. Since then, gaps in understanding have decreased, and development could move forward with less rework. At the same time, we created opportunities for team members to travel to each others offices, increased opportunities for important decision-making and do offline MobPro. Offline MobPro Picture Establishing a Sprint Zero (not directly related to MobPro) We requested the product owner for a Sprint Zero, and we had a one-week Sprint Zero period. In Sprint Zero, we prepared to be able to produce a lot of output at Sprint 1. I particularly focused on the following two things. Create a repository where create-react-app works at a minimum Prepare the deployment destination and GitHub Actions so create-react-app can be deployed In other words, we first created a situation where you can check the operation with one click in the development environment. The ability of continuous deployment allows for better and quicker feedback. By creating a continuous deployment mechanism early on, anyone can deploy the latest code to the development environment at any time. On the contrary, some people went beyond the scope of Sprint 0 and completed Sprint 1 features, which was amazing. Although not directly related to MobPro, it is recommended that a sprint zero be established for any new development project. For more information on Sprint Zero and its various preparations, see [Document Published] Best Practices for Starting a Scrum Project | Ryuzee.com . Analyze Results and Try For the Next Results We were able to complete the development on-schedule while meeting the quality required by the product owner. We were also pleased to hear that they had never experienced a project that was completed on schedule with such high quality. We also had the person in charge of the business department, who is the user of the system, actually test the screen. We got their feedback, reflect it accordingly, and in the end, the user evaluated the system as “very easy to use”. Now, our development team is able to run independently in areas where we are not good at, and we are moving in the direction of further demonstrating our strengths. Analysis for future cases I believe that we have achieved very good results as a project, but considering the reasons for its high reproducibility, I can think of the following points: By aligning common understandings at an early stage, we were able to improve quality and reduce setbacks By creating a foundation for development with a sprint zero, work efficiency was high from the start Through MobPro, we communicated frequently, and relationships were built at an early stage Our early preparation I think, including communicating frequently from early stages and the establishment of a robust development infrastructure, was what led us to success. We were almost expecting MobPro to impact our schedule due to how much it focuses on flow efficiency, but it did not. We believe the reason for this is that we separated the difficult tasks, handled by MobPro, while the easy ones were handled individually. In hindsight, I believe it was beneficial to focus on the bottlenecks in the development process. Try for the next First of all, speaking for ourselves, when we encounter a problem that we cannot solve on our own, we hope to use MobPro with someone who can help us solve the issues. I think this will positively impact both our technical knowledge and teamwork. Also, I would like to try MobPro on a larger scale project. I think that it will be difficult to fully concentrate on improving flow efficiency, but I'm looking forward to see the benefits of establishing a common understanding early on. Summary MobPro is for maximizing flow efficiency, but it was also very useful for unifying common understandings that were not in place. By clarifying where to use MobPro so that we could concentrate on the difficult implementations, we were able to develop the system without causing delays in the project. KINTO Technologies has offices in Tokyo and Osaka. To know more about the people working here, or the type of open positions, please see Job List . We look forward to hearing from you!
アバター
はじめに モバイルアプリ開発グループでアシスタントマネージャーをしているK.Kaneです。 普段は my route PJでPLをしたり、iOSエンジニアをしたりしております。 先日、my route iOSアプリを経路アプリとして、iPhoneにプリインストールされているマップアプリ(以下、標準マップ)から起動できるようにしたリリースを行いました。 実は地図アプリなどを中心に経路アプリ設定されているアプリは結構あるのですが、この辺りの設定方法について日本語で説明しているサイトは意外となさそうでしたので、今回記事にしてみました。 ShareExtensionとの違い iOSにおいて他アプリとデータを伴った連携を行う場合、ShareExtensionを利用するのが一般的です。ShareExtensionを利用すると、他のアプリが対応したファーマットに合致するデータを共有メニュー経由で連携しようとすると、共有先のアプリ一覧に候補として表示されるようになります。 標準マップの場合、スポット検索の結果は「 https://maps.app/com/? 」で始まるユニバーサルリンクとなっており、通常の共有メニューを利用して共有されますので、対応するShareExtensionを用意すれば受け取ることができます。 一方、経路の共有は出発地、目的地データの共有となるため、通常の共有メニューは表示されず、経路アプリとして設定されたアプリにのみ共有することができます。 経路アプリとして設定する方法 経路アプリとしての設定は、Capabilityの追加のみで、ShareExtensionのようなロジックの実装は不要です(後述しますが、受け取ったデータに対する処理については実装が必要です)。 プロジェクトにおいてアプリのTARGETSを選び、「Signing & Capabilities」を選んで、左上の「+Capability」を選択します。 ※画像のXcodeバージョン:15.4 表示された別ウインドウ上でMapsと入力し、表示された2つのうち、「iOS, watchOS」のほうをダブルクリックし、追加します。 「Signing & Capabilities」の下の方に「Maps」が追加されているため、経路を受け取る交通手段を選択します。 以上の設定を行うことで経路アプリの一覧に表示されるようになります。 なお、こちらの設定後のInfo.plistには以下の画像の項目が追加されます。 受け取ったデータの抽出方法 次に経路アプリの一覧経由で起動された際に渡されるデータの受け取り方法について説明します。 受け取りはSceneDelegateクラスで行います。 SceneDelegateクラスがプロジェクト内に存在しない場合は追加してください。ここでは詳しくは触れませんが、追加したクラスをInfo.plistの「Delegate Class Name」にて指定するか、SwiftUIアプリの場合はApp継承のstruct内で@UIApplicationDelegateAdaptorで指定したAppDelegateクラス内から設定することもできます。 import UIKit import MapKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let context = connectionOptions.urlContexts.first, MKDirections.Request.isDirectionsRequest(context.url) { printMKDirectionsData(context.url) } } func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { if let url = URLContexts.first?.url, MKDirections.Request.isDirectionsRequest(url) { printMKDirectionsData(url) } } private func printMKDirectionsData(_ url: URL?) { guard let url else { return } let request = MKDirections.Request(contentsOf: url) if let source = request.source, let destination = request.destination { if !source.isCurrentLocation { print("dep_lat: " + String(source.placemark.coordinate.latitude)) print("dep_lng: " + String(source.placemark.coordinate.longitude)) print("dep_name: " + (source.name ?? "")) } if !destination.isCurrentLocation { print("des_lat: " + String(destination.placemark.coordinate.latitude)) print("des_lng: " + String(destination.placemark.coordinate.longitude)) print("des_name: " + (destination.name ?? "")) } } } } 標準マップからはMKDirections.Requestクラスのデータとして渡されます。 MKDirections.Requestクラスの詳細は こちら を参照していただければと思いますが、departureDateなど他にもプロパティはあるのですが、標準マップから渡されるデータには設定されていないようです。 今回は便宜上データの抽出までをSceneDelegateクラス内で行なっていますが、実際にはそのデータを利用する画面にデータを渡して処理する形になるかと思います。 Apple審査時の追加対応 経路アプリとしてApple審査に出す場合は、App Store Connectの配信タブにある「ルーティングアプリカバレッジファイル」にそのアプリがサポートするエリアを示すgeojsonファイルを追加する必要があります。geojsonファイルの詳細については「ルーティングアプリカバレッジファイル」の項目横の?ボタンを押して表示されるリンク先のページを確認してください。 正しいフォーマットのgeojsonファイルを登録すると以下のようにファイル名が表示されますので、この状態になれば審査に出すことができます。 無事リリース。。? 実際にmy route iOSアプリは以上の設定を行った時点で審査に出し、無事承認されましたのでこの状態で公開しました。 公開したアプリでも無事に連携することができましたので、「以上が経路アプリとしてデータを受け取る方法です。」と言って終わりたいところではありますが、実は後日談がございます。。 見慣れない警告が表示されるようになっている。。 つい先日Privacy Manifestsの対応のため、App Store Connectにアプリをアップロードすると送られてくるメールを確認すると、Privacy Manifestsとは関係ない警告が出ているではありませんか! 以下が実際に送られてきたメールでの警告内容です。 それぞれの警告にあるとおり、Info.plistに以下の設定を追加で行う必要がありました。 MKDirectionsRequestの設定にHandler rankの設定 Supports opening documents in placeの設定 設定後のInfo.plistの内容は以下の画像のようになります。 これらの追加後、上記の警告は出なくなりました。 おわりに my route iOSアプリでは標準マップから出発地、目的地を受け取って、改めてmy route内でそのルートを検索するために使っています。 地図アプリは他のアプリも似たような利用方法が多い印象ですが、地図機能を持たないアプリでも経路アプリとして登録すれば、標準マップとの連携によって出発地、目的地のデータを使うことができますので、アプリの可能性が広がるかもしれないですね。 経路アプリ対応してみようと思っている方にこちらの記事が参考になれば幸いです。
アバター
はじめに こんにちは、 KINTO FACTORY のフロントエンド開発を担当しているきーゆのです。 KINTO FACTORY では、専用のマガジンページを立ち上げるべくヘッドレス CMS の 1 つ Strapi を利用することになりました。 ※この辺りの詳細については、別途記事が投稿されるので楽しみにしていてください! :::message Strapi とは? ヘッドレス CMS でフロントエンドの拡張性が高い 導入コストが低く、標準でコンテンツ取得系の API が提供される OSS のため必要に応じて API の追加や拡張ができる ::: 今回は Strapi 導入時に対応することになった「Strapi にカスタム API を追加する方法」について解説したいと思います。 本記事では、以下の 2 パターンのカスタム API 実装について記載しています。 :::message カスタム API 実装パターンと想定されるユースケース 新規のカスタム API を実装する 複数の collectionType(コンテンツの定義) からエントリを取得して返却したい デフォルト API ではカバーしきれないようなビジネスロジックの実行結果を返却したい デフォルトの API をオーバーライドする エントリ詳細の取得を自動割り当ての postId からカスタムの UID に変更したい ::: Web ページの管理効率化は永遠の課題ですので、本記事をきっかけにエンジニアの血涙が少しでも減らせたらと願うばかりです。 環境情報 Strapi version : Strapi 4 node version : v20.11.0 新規のカスタム API を実装する 本項では新規のカスタム API を実装する方法を紹介します。SQL レベルで実装ができるためカスタマイズ性は高いですが、やり過ぎるとメンテが大変なのでご利用は計画的に。 1. router を作成する 最初に作成する API のエンドポイントに合わせて routes を追加します。 src/api 配下に任意の collectionType ごとのディレクトリ(下図では post )があり、その下に routes ディレクトリがあります。routes 配下に custom-route 定義用のファイルを作成してください。 ※公式によると、必要なファイルを用意してくれる npx strapi generate コマンドがあるみたいです(私は使ってない) 作成したファイルに、以下のようなコードを記述します。 export default { routes: [ { method: "GET", // HTTPメソッドを指します。用途に合わせて適宜変更してください。 path: "/posts/customapi/:value", // 実装するAPIのエンドポイントです。 handler: "post.customapi", // このrouteが参照するcontrollerを指定します。 } }; method HTTP メソッドを指定します。作成する API に合わせて、適宜変更してください。 path 実装するカスタム API のエンドポイントを指定します。 サンプルのエンドポイントの /:value は末尾の値を value 変数として受け取ることを示しています。 Ex) /posts/customapi/1 や /posts/customapi/2 で叩かれた場合、value にはそれぞれ 1,2 が格納されます。 handler 実装するカスタム API が参照する controller(後述)を指定します。 参照したい controller の function 名を指定してください。 2. controller を実装する 1 で実装した routes が参照する controller を実装します。 routes ディレクトリと同階層の controllers ディレクトリ内の post.ts を開きます。 以下のような形で、デフォルトの controller (CoreController) に、前項の routes で指定した handler( customapi )を追加します。 変更前(初期状態) import { factories } from '@strapi/strapi'; export default factories.createCoreController('api::post.post'); 変更後 import { factories } from "@strapi/strapi"; export default factories.createCoreController("api::post.post", ({ strapi }) => ({ async customapi(ctx) { try { await this.validateQuery(ctx); const entity = await strapi.service("api::post.post").customapi(ctx); const sanitizedEntity = await this.sanitizeOutput(entity, ctx); return this.transformResponse(sanitizedEntity); } catch (err) { ctx.body = err; } }, })); 変更内容 デフォルト controller にカスタム handler customapi() を追加 8 行目にて customapi() のビジネスロジックが格納された service customapi() の実行結果を取得 :::message 本項ではビジネスロジックを service レイヤーに移動していますが、controller 内にビジネスロジックを実装することも可能です(再利用性や可読性に応じて定義するレイヤーは変更してください)。 ::: validateQuery(), sanitizeOutput(), transformResponse() の詳細は触れないので、気になる方は Strapi の 公式ドキュメント を参照ください。 3. service を実装する 2 で実装した controller が参照する service を実装します。 controllers ディレクトリと同階層の services ディレクトリ内の post.ts を開きます。 以下のような形で、デフォルトの service (CoreService) に、前項の controller で指定した method (customapi) を追加します。 変更前(初期状態) import { factories } from '@strapi/strapi'; export default factories.createCoreService('api::post.post'); 変更後 import { factories } from "@strapi/strapi"; export default factories.createCoreService("api::post.post", ({ strapi }) => ({ async customapi(ctx) { try { const queryParameter: { storeCode: string[]; userName: string } = ctx.query; const { parameterValue } = ctx.params; const sql = "/** 利用するDB、目的に合わせたSQL */"; const [allEntries] = await strapi.db.connection.raw(sql); return allEntries; } catch (err) { return err; } }, })); 変更内容 デフォルト service にカスタム service customapi() を追加 6 行目にてクエリパラメータの情報を取得 7 行目にてエンドポイントのパラメータ情報を取得 10 行目にて SQL 実行結果を取得 :::message 今回は直接 SQL を実行するために strapi.db.connection.raw(sql) を実装していますが、strapi では他の取得方法も用意されています。 他の取得方法は 公式ドキュメント が参考になります。 ::: 4. 動作確認 以上で新規のカスタム API の実装は完了です。 実際に API を叩いてみて、想定通りの挙動になっているか確認してください。 デフォルトの API をオーバーライドする 本項では、デフォルトで作成されるエントリ詳細取得 API を、オーバーライドにより任意のパラメータで取得できるようにした例を紹介します。 【エントリ詳細取得 API】 [オーバーライド前] GET /{collectionType}/:postId(number) [オーバーライド後] GET /{collectionType}/:contentId(string) 1. router を作成する 新規のカスタム API 実装時と基本的に同じになります。 routes 配下に作成した custom.ts に以下のコードを追記します。 export default { routes: [ { method: "GET", path: "/posts/:contentId", handler: "post.findOne", } }; この route 追加により、 /posts/:postId(number) でエントリ詳細を取得していたエンドポイントが /posts/:contentId(string) でエントリ詳細を取得するようになります( /posts/:postId(number) 経由ではエントリ詳細が取得できなくなります)。 2. controller を実装する controller の実装も新規のカスタム API 実装時と基本的に同じになります。 routes ディレクトリと同階層の controllers ディレクトリ内の post.ts を以下のように変更します。 変更前(初期状態) import { factories } from '@strapi/strapi'; export default factories.createCoreController('api::post.post'); 変更後 import { factories } from "@strapi/strapi"; import getPopulateQueryValue from "../../utils/getPopulateQueryValue"; export default factories.createCoreController("api::post.post", ({ strapi }) => ({ async findOne(ctx) { await this.validateQuery(ctx); const { contentId } = ctx.params; const { populate } = ctx.query; const entity = await strapi.query("api::post.post").findOne({ where: { contentID: contentId }, ...(populate && { populate: getPopulateQueryValue(populate), }), }); const sanitizedEntity = await this.sanitizeOutput(entity, ctx); return this.transformResponse(sanitizedEntity); }, })); 変更内容 デフォルト controller にカスタム controller findOne() を追加 12 行目にて contentID カラムが contentId に一致するレコードを抽出 11 行目にて .findOne() を利用しているため、取得結果は 1 つのオブジェクトになる :::message 13~15 行目はデフォルト API で提供されている populate パラメータ適用に準拠した処理です。 mediaLibrary から動画や画像を取得したい場合は、 populate の付与は必須になるので注意してください。 ::: 本項では、ビジネスロジックを service ではなく controller に実装しています。 3. 動作確認 以上でデフォルト提供している API をオーバーライドする実装は完了です。 実際に API を叩いてみて、想定通りの挙動になっているか確認してください。 まとめ これにて Strapi でのカスタム API 実装の解説は以上になります。 Strapi はカスタマイズ性が高く良いツールだと思います。 そのため今後もナレッジをシェアできればと思いますし、皆様のナレッジもぜひシェアしていただけると嬉しいです。 まだ他にも、 Strapi の記事公開時に自動でアプリケーションをビルドする CKEditor に動画(.mp4)を埋め込めるようにする といったネタがありますが、また別の機会に。 読んでいただき、ありがとうございました。
アバター