TECH PLAY

KINTOテクノロジーズ

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

936

Introduction Hello, I’m Koji Nishiguchi, Manager of Analysis Group at KTC Data Analysis Division. Exploring how future-focused research and technology development can address real-world challenges is a key driver of innovation. However, a disconnect often arises between researchers and businesses regarding "research aimed at future needs" and "technologies ready for immediate on-site application." This gap makes it difficult for both parties to collaborate effectively. In this article, we will highlight our efforts to bridge this "matching gap" and share our vision for future initiatives. (Generated by Microsoft Copilot) Activity so far Toyota Motor Corporation operates a division called the Future Creation Center (FRC), dedicated to conducting future-focused research. Through a fortunate connection with the FRC, they joined us at the Data Analysis Division of KINTO Technologies (KTC) as collaborators, assisting with research challenges. This marked the beginning of our initiative. Initially, we addressed specific data science challenges and were able to advance the project smoothly as a novel research endeavor. In traditional data science issues, as described in textbooks, the focus is primarily on the predictions themselves as the main metric of evaluation. However, from a business perspective, the reliability and variability of predictions also play a crucial role. In other words, it is crucial to understand the confidence level of predictions. Over time, several challenges emerged, prompting us to expand our efforts further. At the start, these challenges were relatively manageable, and we were able to turn them into research projects that made meaningful contributions to business outcomes, with the valuable support of FRC. The challenges of matching However, a problem arose. It became clear that the constraints and priorities of the projects were different for each side. While both teams aimed to improve user experience, their goals were not the same: KTC focused on product commercialization, while FRC aimed to conduct research for the future, such as securing patents or publishing papers. These differences created challenges, such as mismatched timelines and varying expectations for the level of completion. To work within these constraints, we often had to focus on “technologies that were nice to have but not essential.” As a result, the developed technologies were deprioritized in practice and remained unused. This experience highlighted the challenges of establishing effective collaboration between the "research team" and the "business field." (Generated by Microsoft Copilot) Conducting the ideathon 1. Expectations for the ideathon As one of the solutions, we organized an ideathon. Ideathon is a workshop-style event designed to generate and develop new ideas for tackling challenges in a short period of time. This event brought together participants from both FRC and KTC, aiming to explore ways to leverage each other's technologies and research through open, creative discussions. In the medium and long term, we hoped this collaboration would help both parties identify opportunities for cooperation. For KTC, this meant gaining fresh perspective on how to apply new technologies, while FRC could discover potential "seeds" for future research projects. Purpose of the initiative Expectations for the ideathon Research side Research needed for the future Discovery of seeds for future projects Business side Technologies that can be used immediately Gaining new perspectives 2. Implementation process To prepare for the ideathon, FRC created a list of technologies that could be introduced. KTC members then provided feedback through a survey, and based on the results, two technologies were selected for the event. On the day of the ideathon, we began with an introduction of the two technologies, followed by a short Q&A session, and then moved into the main ideathon activity. 3. Event summary Date: September 2024 Schedule: 16:00-17:30 Study session (Introduction of two technologies) Technology A: Recommendation-related technology Technology B: Customer psychology measurement technology 17:40-19:00 Ideathon (25 minutes per table) Participants: ・FRC: 7 members (1 online) ・KTC: 11 members (3 online) During the ideathon, KTC participants were divided into three groups, rotating through three discussion tables: Technology A, Technology B, and a free discussion table. Each table consisted of 2-3 FRC members and 3-4 KTC members. Most participants were meeting for the first time, so it began with self-introductions and overviews of each participant’s work. This was followed by discussions focused on the table’s theme. The 25 minute sessions went by in no time, and in many cases, discussions were just starting to gain momentum when time ran out. Even so, a project with potential for matching emerged, and FRC researchers and KTC engineers quickly began aligning on the details. While we cannot share specifics, we felt that if KTC's pursuit of "customer understanding" and the research department’s technologies were well-aligned, meaningful progress could be made. 4. Feedback from participants and suggestions for improvement After the event, we conducted a survey among KTC participants. The satisfaction score was 4.11 out of 5. With the condition of "If the content is interesting," all participants said they would like to join the next event. This shows that the event was meaningful for everyone. However, the following suggestions for improvement were made: The explanations during the study session were too long. The audio quality in the online presentations was poor. The study session should include examples using actual KTC data. More concrete examples are needed. The ideathon sessions should be longer. The self-introductions and work overviews at the beginning of each ideathon session took too much time, leaving less time for discussion. We will take these suggestions into account to make future events smoother and more effective. Future prospects In addition to the FRC, the Toyota Group has many other research units. When opportunities arise, we aim to exchange ideas with other research teams and create spaces for effective matching. Specifically, we plan to clearly present the technologies and solutions required by the business field, while offering researchers opportunities to apply their work in real-world business contexts. At the same time, by communicating researchers’ interests, such as themes and challenges, to companies, we believe we can bridge the gap between research and business, fostering mutual understanding. Through these efforts, we aim to build a win-win relationship by increasing the number of cases where technologies are effectively applied in practical settings. To achieve this, we must continue to accumulate experience through ideathons and matching events, making collaboration between technology and research smoother. We believe this will speed up the process of bringing future-focused research into practical use and help create a better society. Conclusion To connect future-focused research with technologies needed today, it is important to create spaces where the needs and expectations of both sides can be understood. Through study sessions, ideathons, and workshops like those mentioned, we believe that fostering mutual understanding between researchers and the business side can pave the way for more effective and practical collaboration. By fostering such collaboration, we believe that integrating various new technologies into KTC’s vision of an "in-house development organization with a customer-centric perspective" , will enable us to deliver inspiring new experiences to our customers at the earliest. KTC will continue to work with FRC to develop new technologies and explore the practical applications. This is essential for us, as a leading mobility platform provider, to bring not just “mobility” but also “inspiration” to every journey. (Generated by Microsoft Copilot) <Unauthorized reproduction or use of the content, text, images, etc., on this site is strictly prohibited.>
アバター
Introduction Hello, I am Osanai, the leader of the SRE Team in the Platform Group at KINTO Technologies (hereinafter, KTC). In this article, I will discuss how we developed the SRE Team’s mission and vision. If you’re only interested in the finalized mission, feel free to skip directly to here . Why Did We Decide on a Mission and Vision? Three key factors influenced our decision to establish a mission and vision this time. 1. A suggestion from a team member The SRE Team was established in January 2021, but after various ups and downs, it became a one-person team by the end of March. In April, a new team member suggested creating a mission and vision for the team, which sparked the initiative. He mentioned that, in his previous job, the company’s mission and vision were well-integrated into daily operations and functioned effectively. However, at the time, I didn’t see an immediate need for it and largely dismissed the idea as something we could address later if time allowed. (Apologies!) 2. The need to present a team roadmap to senior leadership. At the time, the SRE team consisted of just two members with limited resources, so we were actively working on initiatives to enhance recruitment efforts. As part of this effort, it became essential to clearly communicate to senior leadership what the SRE Team aims to accomplish, the challenges we anticipate, and the number and types of personnel required to address them effectively. As part of this process, we decided to create a roadmap for the SRE Team. Additionally, we concluded that establishing a mission and vision would provide a higher-level framework to guide the team’s activities. 3. Diversification of the term “SRE” Based on that, we started looking at the missions and visions of various companies’ SRE teams. Then, we came across a slide like this: From “What Does SRE Aim to Do?” Personally, this struck a chord with me the most. At KTC, there are several other cross-functional teams and groups in addition to the SRE Team, such as Platform Engineering, Cloud Infrastructure, DBRE, CCoE, and Security. The term "SRE" encompasses a broad range of responsibilities, so I felt it was important to define our specific role and focus within the context of the existing environment around us. How Did We Decide? So, in light of all the above, we decided to come up with a mission and vision. However, having no guidelines on how to go about it, we were fumbling in the dark to begin with. First, regarding the time commitment and timeline for the decision-making process, we decided to complete it within a relatively short period. This was partly because we anticipated that the process might drag on if we chose to make decisions incrementally during weekly meetings. We considered dedicating a full day to the process but realized that our ideas might be overly influenced by the conditions on that particular day. Ultimately, we decided to allocate one hour per day over five business days, with a weekend in between, to allow for a more balanced and thoughtful approach. After going through the process, my personal impression is that spreading it out over several days was a good decision. It gave me time to reflect, and I came up with ideas while taking a bath and just before falling asleep. Next, in terms of how we decided, we opted to base our approach on Google re:Work . We proceeded by following “Set and communicate a team vision” in the “Managers” theme. This time, our goal was to formulate a mission and vision. To achieve this, we proceeded by defining the core values, purpose, and mission within the relevant sections. For the vision, we approached it from the perspective of imagining what we would want the SRE Team and the company as a whole to look like once the mission had been accomplished. Therefore, we chose to define the vision based on the mission we had established. Day 1: Identifying What Values We Want to Cherish On the first day, we decided to start by identifying the values that each team member personally wants to cherish. Using Miro as our collaboration tool, each of us wrote on sticky notes the things we want to cherish, encompassing both technical and non-technical aspects. Coming up with ideas about the things you want to cherish can be quite challenging, so I realized that an alternative approach could be to identify ideas by considering the outcomes you would most want to avoid. After this, we had an open discussion about the values embodied by people we admire. Day 2: Doing a Deep Dive into the Core Values of the Team On the second day, we each shared and discussed the values we had identified on the first day. Even when the details differed, we discovered that by gradually abstracting our thoughts through questions like “Why do you want to cherish that value?”—similar to a why-why analysis—we arrived at shared underlying values. Taking note of these insights as you go could be helpful when defining your own mission. Next, we discussed the values we resonated with and brainstormed concrete behaviors that reflect those values. Since we were doing it in a pair this time, we picked out some values that we could relate to from among each other’s, and did a deep dive into those. For example, one of the values was along the lines of “Produce better output through good collaboration,” but that sounded a little abstract. So, we reworded it more concretely by asking what “good collaboration” and “better output” actually meant, and as we did so, we got a much clearer image of what it was about. Day 3: Exploring the Reason Why the Team Exists On the third day, we thought about the purpose (the reason why the team exists). We collaboratively answered six questions under the theme “Why Does This Team Exist?” and discussed our ideas along the way. One thing I felt requires some caution is that these questions focus on the current status quo, so people's answers about the fundamental reason for the team's existence might be biased especially if they are motivated to bring about changes to the current organizational situation. By reflecting on what we had done so far, abstracting those actions, and revisiting the reasons behind them, we started to uncover a potential candidate for the team’s fundamental reason for existence. Day 4: Deciding on a Mission On the fourth day, we finally decided on our mission. First, we each wrote on sticky notes our own thoughts about three questions, as a form of self-reflection . Then, we decided on a mission in light of everything we had done since the first day. To be honest, there were moments when we relied on inspiration, but we reviewed all the activities and conversations we had engaged in up to that point. From those, we identified phrases that seemed to stand out as potential keywords and crafted a mission statement that encapsulated and satisfied them. In making our decision, we also kept in mind whether our ideas aligned with the five key characteristics that a mission should have. Day 5: Deciding on a Vision On the fifth day, we decided on a vision by imagining what we wanted the SRE Team and the company as a whole to be like once we had achieved the mission. The Miro board we actually created (just the atmosphere of it) The Mission and Vision We Decided On The mission and vision we actually decided on are as follows: To explain the mission, first, I will look at the part about being able to provide products as fast as possible. KTC has a wide variety of products. We would like to enable them to provide their functions to users as quickly as possible, and create an environment where we can get feedback. However, just providing products quickly is not enough: we also need to deliver to users products that will be of value to them. In addition, no matter how quickly you can provide a valuable product, it will be meaningless if the users cannot use it to their satisfaction. So, we added the word “reliable.” For our vision, we envisioned what KTC would look like once it achieves the ability to provide reliable, highly valuable products as quickly as possible. We concluded that to simultaneously fulfill the quality-focused aspect of "reliable, highly valuable products" and the speed emphasized in "provide [them] as fast as possible," it is essential to strike the right balance between development and operations, guided by service level requirements. In Conclusion We managed to formulate a mission and vision for our team without a hitch. Although it's still early days since we established it, conversations are already emerging around questions like, "Considering our mission, is this something we should really pursue?" and, "If we decide to do this, to what extent should we take it?" This gives me the sense that the mission is beginning to serve effectively as a guiding framework for our team. That said, simply formulating them now doesn’t mean this is the end of the journey. I would like us to develop a roadmap for achieving our mission and vision and work together toward them as a unified team. Also, the SRE Team is looking for people to work with us. If this has sparked even a slight interest, please don’t hesitate to reach out to us. We look forward to hearing from you! https://hrmos.co/pages/kinto-technologies/jobs/1811937538128224258
アバター
はじめに こんにちは!iOSエンジニアのViacheslav Voronaです。今年の8月に東京で開催されたiOS開発者向けカンファレンス iOSDC Japan 2024 に参加しました。 セッションがほとんど日本語で行われるカンファレンスには初めて参加したので、少し緊張していましたが、実際に参加してみると、思ったよりもスムーズに理解できました。コードスニペットを見たり、自分がある程度理解しているトピックについて話を聞いたりすると、たとえすべての言葉を聞き取れなくても、意外と内容を追うことができました。スライドに英訳を入れてくれた方々に感謝です!これ以上ないくらい助かりました❤️ 今回はいくつかのセッションに参加しました。その中には、登壇者が趣味として取り組んでいるプロジェクトを紹介するものもありました。例えば、 ta.inoue さんの セッション では、GPSの仕組みを解説しつつ、 iOSデバイスを使って送信データのスニッフィングを実演していて、とても興味深い内容でした。GPSは原理としてはシンプルなのに、実際の構築は驚くほど複雑であるという点がいつも私を魅了します。このセッションも期待を裏切らないものでした。 また、 haseken さんによるUIKitのさまざまな ViewControllersの歴史や役割を 深堀り したセッションも印象的でした。日々の業務でViewControllerを使うことは多いですが、UIKitは非常に幅広く、用途が限られたView Controllerの中には知られていないものも結構あります(私も初めて知るものがいくつかありました)。このセッションもとても興味深かったです。 今日は、これらのセッションの中から、実務で役立つ可能性があり、今後も心に留めておきたいと感じたポイントをいくつかご紹介したいと思います。それでは、いってみましょう! 隠れたAPIを活用する実践例 どの開発者も、時おり、プロジェクトで使用しているサードパーティのコードを詳しく調べる必要に迫られることがあります。新しいプロジェクトのニーズにそのライブラリやフレームワークが合っているか確認するため、既存のツールをより深く理解するため、時にはサードパーティのコードのバグを追跡して報告するためなど、その理由はさまざまです。 しかし、私たちが日々使用していながらも謎めいているのが iOSフレームワーク(SwiftUIやUIKitなど)の存在です。これらのフレームワークには、十分に説明され推奨されているAPIだけではなく、まだ知られていない多くの機能が隠されています。こうした機能を見つけて活用することは、単なる興味深い挑戦にとどまらず、特定の場面でiOS開発者にとって有益な結果をもたらす可能性があります。これが noppe さんによるセッション 「iOSの隠されたAPIを解明し、開発効率を向上させる方法」 のテーマでした。 noppeさんのプレゼンテーションは次の3つのパートに分かれていました。 Perform(発見) :最初のパートでは、隠されたメソッドをどのように見つけるかについて簡単に説明がありました。主な手法は以下の通りです。Objective-C: 興味のあるクラスの .h ファイルを置き換える。Swift: .tbd や .swiftinterface ファイルを調整する。 Use Case(ユースケース) :2つ目のパートでは、隠れたAPIを使う可能性のあるシナリオが紹介されました。このパートが最も長く、以下のような具体的なケースが挙げられていました。 プロトタイピング: 便利で安全。非クリティカルなコードであれば、効率を優先して活用できる。たとえAPIが変更されても機能に影響しない。 テスト: 便利で比較的安全。特定のテストシナリオを簡単にカバーできる。ただし、非公開API自体にもテストを追加して、変更をすぐに検出できるようにするのが望ましい。 本番環境 : 明らかに安全ではない。将来的にAPIの挙動が変わる可能性があるうえ、App Storeの審査でリジェクトされるリスクもある。 Find(発見方法) :最後のパートでは、隠されたAPIを発見する具体的な方法について説明されました。主な手段として、 .h 、 .tbd 、 .swiftinterface ファイルの確認や、スタックトレースの分析、さらにコミュニティでの情報共有が挙げられています。 noppeさんのプレゼンテーションでは、隠されたAPIが役立つ具体例もいくつか紹介されましたので見ていきましょう。 プロトタイピングの段階で印象的だったのは、 UITextView に関連する例です。ご存じの通り、テキストビューにプレースホルダーを設定する機能は一般的ですが、Appleはそれを直接設定するためのAPIを提供していません。そのため、通常はラベルをサブビューとして追加するなど、カスタムソリューションを作成する必要があります。そこで登場するのが UITextView です。公開されていない setAttributedPlaceholder というメソッドがあり、これを使えばプレースホルダーを簡単に設定できます。このメソッドは本番環境での使用は許可されていませんが、プロトタイピングや概念実証の段階では時間を節約することができます。 テストに関連する例として挙げられたのは、 UIDebuggingInformationOverlay です。このツールは、iOS 11以前では簡単に使えましたが、現在ではアクセスするために 少し工夫 が必要です。それでも、UIKitの隠れた低レベルな機能を使えば、有効化することが可能です。 UIDebuggingInformationOverlay 非公開APIについて学ぶことは、普段使っているツールへの理解を深める良い方法です。本番環境での使用は推奨されませんが、そうしたAPIの存在を知ることで、通常とは異なる解決策が求められる場面で新たな可能性が広がります。また、それらの仕組みを知ることで、自分のAPI設計スキルを磨くことにもつながります。 このセッションは非常に実用的で、多くの刺激を受けました。参加できて本当に良かったと思います。 @ speakerdeck Server-driven UI 開発者として、私たちは常に業界の最新トレンドを追いかけています。それは、新しいフレームワークや技術をいち早く試してみたいという好奇心からだったり、同僚に遅れをとりたくないという理由だったりします。しかし、今年のiOSDCで私は、これまでほとんど知らかったデザインパラダイムについてのセッションに出会いました。その手法自体は以前から存在していたものの、これまで深く理解する機会がありませんでした。セッションはNadeさんによる サーバードリブンUI入門: 画面のStateを直接受け取るアプローチ というものでした。サーバードリブンUIは少しニッチな技術に思えますが、その存在を知り、注目しておくだけでも価値があると感じました。 サーバードリブンUIとは、UIの状態をバックエンドから直接受け取る仕組みです。このアプローチを使えば、アプリの新しいバージョンをリリースすることなくクライアントサイドのUIを変更できます。また、クライアント側のビジネスロジックを減らすという点でも非常に有用です。仕組みとしては、クライアントアプリ内にシンプルで再利用可能なUIコンポーネントをあらかじめ実装しておき、それらをサーバーのレスポンスによって制御します。サーバー側で、どのコンポーネントをどの画面に、どの順序で表示するのか、そしてそれぞれにどんな内容を含めるのかを指示します。 iOSの場合、SwiftUIを使用すると、ビューが Codable プロトコルに準拠できるため、サーバーの応答をそのまま反映させる形で実装することができ、このアプローチはより自然に感じられます。Nadeさんのプレゼンテーションでは、この点が明確に示されていました。 もちろん、このようなパラダイムを採用することで、伝統的な実装と比べていくつかのトレードオフが生じます。それでは、それらを簡単に見ていきましょう。 Server-driven UI の利点: 新しい機能をリリースする際にアプリの新バージョンを配布する必要がなく、バックエンド側で変更を加えるだけで済むため、リリースサイクルが短縮されます。 ビジネスロジックが全てのプラットフォームで統一されることで、 DRYer コードが実現します。これにより、 変更が即座に全てのクライアントに反映されます。 クライアント側でのビジネスロジックが最小限(理想的にはゼロ)になるため、開発者はUIやUXの細部に集中し、よりスムーズな体験を提供することに注力できます。 Server-driven UI の課題 事前に多くの汎用的なUI要素を定義して構築する必要があり、小規模なチームにとっては負担が大きい。 これらの汎用UI要素のすべての組み合わせをテストするのには、多大なコストがかかる。 サーバーサイドのアーキテクチャが、追加の「Backend for Frontend」レイヤーによりさらに複雑化する。 このように、サーバードリブンUIは、バックエンドの複雑さやインフラを管理できる大規模なチームに最も適したソリューションですが、クライアントの柔軟性や配信速度の向上という点では大きなメリットを提供できます。 @ speakerdeck 最後に iOSDC Japan 2024は、私にとってとても楽しめたうえに有益な体験でした。今回ご紹介した2つのセッションは、私が参加した中でも特に実務で役立つ内容でした。もし興味を持たれたなら、iOSDCの主催者がセッション録画を公開しているので、ぜひチェックしてみてください。 最後までお読みいただき、ありがとうございました。それでは、また次回!
アバター
This article is part of day 3 of KINTO Technologies Advent Calendar 2024 .🎅🎄 I am Somi, a Flutter application developer at KINTO Technologies (hereinafter, KTC). Flutter is an appealing framework that enables you to construct a diverse range of UIs independently of the platform. In particular, with CustomPaint, you can easily produce intricate designs that would be difficult to achieve with the basic widgets alone. Recently, when implementing a QR code recognition screen, an issue arose with creating a border for the recognition area. We tried to use the existing libraries, but there was a limit to how well they could achieve the curve design we wanted. Therefore, we solved the problem by drawing the border directly using CustomPaint and Path . In this article, I will detail what steps we took to complete the border for the QR code recognition screen using CustomPaint and Path. The Goal of the Border Design The border we implemented this time is a curved, translucent white one around the four corners of the QR code recognition area. Using the CustomPainter class , we defined a path on the Canvas with Path, then drew the border using a combination of curves and straight lines. Preparations for Drawing the Border Using CustomPainter First, we define a class for drawing the border, namely, _OverlayPainter . This class extends CustomPainter, and will be responsible for drawing the border on the Canvas. The following is sample code for drawing a border using an already defined _OverlayPainter. Later, I will explain in detail about the specifics of implementing it. class QrScanPageContent extends StatelessWidget { const QrScanPageContent({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("QR Code Scanner"), // Screen title ), body: CustomPaint( size: Size.infinite, // Draw it to fit the size of the whole screen painter: _OverlayPainter( squareSize: 200.0, // Size of the border area borderRadius: 20.0, // Roundness of the border’s corners borderThickness: 8.0, // Border thickness ), ), ); } } Setting up the background and recognition area We will create in specific detail the _OverlayPainter I mentioned above. First, we draw the background color and the QR code recognition area. We draw the background as a semi-transparent rectangle using the drawRect method, and the QR code recognition area as a rounded rectangle using the drawRRect method. For drawing each, we set the style (color and transparency) using the Paint class. In the next section, I will explain how to draw the border in detail. class _OverlayPainter extends CustomPainter { final double squareSize; final double borderRadius; final double borderThickness; _OverlayPainter({ required this.squareSize, required this.borderRadius, required this.borderThickness, }); @override void paint(Canvas canvas, Size size) { final centerX = size.width / 2; final centerY = size.height / 2; // Draw the background final backgroundPaint = Paint()..color = Colors.grey.withOpacity(0.5); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint); // Draw the recognition area final rect = RRect.fromRectAndRadius( Rect.fromCenter( center: Offset(centerX, centerY), width: squareSize, height: squareSize, ), Radius.circular(borderRadius), ); final innerPaint = Paint()..color = Colors.lightBlue.withOpacity(0.1); canvas.drawRRect(rect, innerPaint); // Here, we set the frame’s style and draw the frame. } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } The shouldRepaint method decides whether this CustomPainter requires redrawing. In this example, the background color and the size of the recognition area are fixed, so redrawing is unnecessary. Consequently, this method always returns false. However, if you want to draw dynamically or change the size or shape, you need to set this method to true. Setting the border style Next, to get ready to draw the border, we set the line style. Before drawing the border, we define the style using a Paint object. The Paint class provides tools for setting things like the color, thickness, and shape of a line. Here, we set the border to translucent white and define the line shape to be rounded. By setting the border to a translucent white color, we have ensured that the important recognition area can be spotted at a glance. final borderPaint = Paint() ..color = Colors.white.withOpacity(0.5) // Set the border color and transparency ..style = PaintingStyle.stroke // Set the outer border style ..strokeWidth = borderThickness // Line thickness ..strokeCap = StrokeCap.round; // Set the ends of the line to be rounded Calculating the coordinates and sizes To draw the border, we first need to calculate the coordinates and size of each corner. This will enable us to define the start and end points of each corner precisely. The following is an example calculation: const double cornerLength = 55; // Length of each corner double halfSquareSize = squareSize / 2; // Size of half of the recognition area double left = centerX - halfSquareSize; // Left boundary double right = centerX + halfSquareSize; // Right boundary Double top = centerY - halfSquareSize; // Top boundary double bottom = centerY + halfSquareSize; // Bottom boundary Coordinate system : In Flutter’s Canvas coordinate system, the top left is (0, 0). This means that the top side is calculated using centerY - halfSquareSize , and the bottom side is defined as centerY + halfSquareSize . cornerLength : Define what length of straight line to draw at each corner. halfSquareSize : Calculate the size of half of the QR code recognition area. left, right, top, bottom : Define the boundary coordinates of the recognition area with respect to the coordinates of the center. Pictorially, the above formulas look like the following figure. Drawing the Border First, we start to draw from the top left corner. To draw the top left corner, we define a path using the Path class. Path is a handy class that lets you specify a variety of shapes like lines, curves, and arcs, then draw them on the Canvas. 1. Draw a straight line from right to left We move the start point to the top end of the corner, then draw a straight line going left. Path topLeftPath = Path(); // Define a new path topLeftPath.moveTo(left + cornerLength, top); topLeftPath.lineTo(left + borderRadius, top); The above code results in drawing a line like the one below. 2. Draw a corner curve We add a curve that starts from an end point of the straight line. Using the arcToPoint method, we draw a curve from the start point to a specified end point. This enables us to create a natural join from the straight line to the curve. In the code below, we set the curve’s end point with Offset and its radius with Radius, giving us a rounded corner for the QR code area. topLeftPath.arcToPoint( Offset(left, top + borderRadius), // End point of the curve radius: Radius.circular(borderRadius), // Radius of the curve clockwise: false, // Draw the curve counterclockwise ); The code above generates a curve with a rounded corner, as shown below. 3. Draw a vertical straight line We extend a line downward from the endpoint of the curve. topLeftPath.lineTo(left, top + cornerLength); The code above adds a vertical line, as shown below. 4. Draw the path on the Canvas We use the defined Path to draw on the Canvas with borderPaint. canvas.drawPath(topLeftPath, borderPaint); Processing the remaining corners The following is a code example demonstrates how to draw the other three corners after the top left one: // Bottom left corner final bottomLeftPath = Path() ..moveTo(left + cornerLength, bottom) ..lineTo(left + borderRadius, bottom) ..arcToPoint( Offset(left, bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: true, ) ..lineTo(left, bottom - cornerLength); canvas.drawPath(bottomLeftPath, borderPaint); // Bottom right corner final bottomRightPath = Path() ..moveTo(right - cornerLength, bottom) ..lineTo(right - borderRadius, bottom) ..arcToPoint( Offset(right, bottom - borderRadius), radius: Radius.circular(borderRadius), clockwise: false, ) ..lineTo(right, bottom - cornerLength); canvas.drawPath(bottomRightPath, borderPaint); // Top right corner final topRightPath = Path() ..moveTo(right - cornerLength, top) ..lineTo(right - borderRadius, top) ..arcToPoint( Offset(right, top + borderRadius), radius: Radius.circular(borderRadius), clockwise: true, ) ..lineTo(right, top + cornerLength); canvas.drawPath(topRightPath, borderPaint); Summary Besides precise designs like the QR code border, using CustomPaint and the Path class enables you to create even more complex UI designs as well. Implementing the border for the QR code recognition screen ourselves reaffirmed how flexible Flutter is, and power of its Canvas functionality. However, when using CustomPainter, keep in mind that more complex your drawing logic can impact performance. If frequent redrawing is necessary, consider optimizing the processing and utilizing other existing widgets. I hope this article can serve as a useful reference for implementing UI design using CustomPaint and Path.
アバター
This article is part of day 4 of KINTO Technologies Advent Calendar 2024 Merry Christmas ✌️ Ryomm, who works on developing My Route (iOS) at KINTO Technologies (referred to as KTC), is stepping into a new role this time! Allow me to introduce "Manabyi," an incredibly exciting Slack bot created in collaboration with the Michi-no-eki project, as I take on the role of a phantom bot craftsman. What is manabyi It's an incredibly exciting Slack bot designed for gathering information on internal study sessions and events, as well as leveraging the collected data effectively. It contains all of the event-related aspects. ![Manabyi](/assets/blog/authors/ryomm/2024-12-04/01.png =200x) There are two main roles of Manabyi: Search for events Register and publicize new events When a new launch event is entered into Manabyi, it automatically announces the event to the relevant channels. Users can stay informed about internal events either by following the designated channels or by reaching out directly to Manabyi. You don't need to be a direct stakeholder to access the information. The use of Manabyi will be covered in the [Learning Road Station Series] on a later date. In this article, I'll focus on introducing the technology behind Manabyi. Manabyi's technique Manabyi is created using the Slack CLI. https://api.slack.com/automation/quickstart The Slack CLI simplifies the process by eliminating the need to build infrastructure, such as setting up a datastore on Slack's side. I also appreciate that the development environment can be set up directly on the Slack CLI side. The general structure of Manabyi is outlined as follows: Trigger There are three triggers in the manabyi. Features The type of trigger Add an event Link trigger Delete an event Link trigger Search for events Event trigger ![Link Trigger](/assets/blog/authors/ryomm/2024-12-04/03.png =500x) ![Link Trigger Form](/assets/blog/authors/ryomm/2024-12-04/04.png =500x) ![Event Trigger](/assets/blog/authors/ryomm/2024-12-04/02.png =500x) There are four types of workflow triggers in Slack. Trigger name Description Link trigger Once created, a URL will be issued and executed when the link is clicked in Slack (not valid outside Slack) Scheduled trigger Run in hours Event trigger Mentions and reactions Webhook trigger Executed when a specific URL receives a POST request https://api.slack.com/automation/triggers Manabyi is primarily designed for users to utilize the search function, while the add/delete functions are intended exclusively for event organizers. To prevent accidental additions or deletions of events, we have separated the types of triggers accordingly. When you register for an event, notifications are sent to two channels: the channel that invoked the bot and the channel designated for promoting the subscription. ( #notice-new-event ). ![Notifications](/assets/blog/authors/ryomm/2024-12-04/05.png =500x) This ensures that you're informed about newly launched events, regardless of the channel where they were created. Of course, you can also retrieve information by interacting with Manabyi using keywords of interest. Block Kit Slack uses a framework called Block Kit to create rich visual messages. You can experience it with the following tool called Block Kit Builder, so if you have never used it before, please give it a try. https://api.slack.com/tools/block-kit-builder The result of getting event information is also easier to use Block Kit. https://api.slack.com/block-kit ![Message using Block Kit](/assets/blog/authors/ryomm/2024-12-04/08.png =500x) The reference page links, such as those directing to a Slack channel, a Confluence page, or a kick-off video, can often be lengthy and cluttered. To address this, we streamlined the experience by embedding them under a "Details" button. The description text field is configured to use the rich_text type to accommodate requests for including links alongside plain text. Slack bot development tips corner Start by deciding whether or not to use Block Kit when sending messages. Initially, I used the string type but later decided to switch to the rich_text type. However, since the string type and rich_text type are not compatible, migrating the datastore was not a straightforward process. Moreover, once the registered data is converted to rich_text, I want to incorporate features like line breaks and links! After considering various factors, including those mentioned, we decided to take a bold approach by clearing the data in the datastore and starting fresh. Super exciting! If you can decide, it is a good idea to decide whether to use the Block Kit first. https://api.slack.com/automation/datastores Be careful with block_id For more information, go to this article: [ Slack CLI ] block_id conflicts and cannot send messages to Slack Workflows run only at build time For testing purposes, when creating a method that uses a UUID, you should pass the id as an argument from outside the method. const addEventFunctionStep = AddEventWorkflow.addStep( AddEventFunction, { id: Crypto.randomUUID(), // you want to generate an ID on the caller of the method title: formData.outputs.fields.title } ) However, the Workflow definition is executed only during build time, while the function is executed during subsequent calls. For this reason, any code that needs to be executed each time, such as generating a UUID, should be included within your method. Documents are too hard to find The Slack API documentation can generally be applied as is without modifications. In particular, I often explore what types can be used in trigger inputs, forms, and datastores. For you, this document is the solution! https://api.slack.com/automation/types Technology that supports manabyi Manabyi is currently treated as an inner source, and since the development environment is well-prepared, I’ll provide an introduction. CI/CD Test Slack CLI runs on Deno, so you can run tests with deno test . name: Slack App Test on: pull_request: types: [opened, synchronize, reopened, ready_for_review] jobs: build: runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Install Deno runtime uses: denoland/setup-deno@v1 with: deno-version: v1.x - name: Install Slack CLI if: steps.cache-slack.outputs.cache-hit != 'true' run: | curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash - name: Test the app run: | cd app/ deno test --no-check Deploy You can get a service token starting with xoxp- by running the slack auth token command in your console. Register this as a secret on GitHub. The workflow is defined as follows: name: ‍➡️ Slack App Deploy on: push: branches: [ main ] workflow_dispatch: jobs: build: runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Install Deno runtime uses: denoland/setup-deno@v1 with: deno-version: v1.x - name: Install Slack CLI if: steps.cache-slack.outputs.cache-hit != 'true' run: | curl -fsSL https://downloads.slack-edge.com/slack-cli/install.sh | bash - name: Deploy the app env: SLACK_SERVICE_TOKEN: ${{ secrets.SLACK_SERVICE_TOKEN }} run: | cd app/ slack deploy -s --token $SLACK_SERVICE_TOKEN You can now deploy regular app code updates. However, it's important to note that trigger updates and datastore configuration changes cannot be automated, so these deployments must be handled manually. Issue Template Templates are provided to facilitate the submission of bugs, feature requests, and questions. To be honest, it's rarely used, but I appreciate it because it feels very OSS-like. Project We use the GitHub Project. We primarily use Jira for our internal projects, but since Manabyi doesn't utilize Confluence, it's more convenient to submit issues on GitHub. (I also wanted the team to experience the benefits of GitHub Projects.) Therefore, I decided to manage everything entirely on GitHub. Since I belong to the Mobile Development Group, my focus is primarily on Swift and Kotlin. As a result, many team members find writing TypeScript to be a challenging hurdle. Additionally, it's challenging to raise awareness about the existence of such a project outside the department, leaving many issues to address as an inner-source project. I am setting up a development environment with the hope that, as Manabyi continues to grow, someone will contribute to it someday. Conclusion That was an introduction to Manabyi. Manabyi is still in its infancy, but I hope it will grow alongside the culture of KTC...!
アバター
Introduction Hello! I am high-g ( @high_g_engineer ) from the New Car Subscription Development Group at the Osaka Tech Lab. In this article, I reflect on the Developers Summit 2024 KANSAI, held on September 18, 2024. What Is Dev Summit KANSAI? The original Developers Summit (hereinafter, Dev Summit) is a conference-type event for software developers that has been held annually in Tokyo and online since 2003. Dev Summit KANSAI has been held since 2011 as a spin-off event of that, and is a much-loved festival for IT engineers in Kansai. The theme for 2024 was “Let’s create the new standard together.” The sessions covered a wide range of topics, including security, AI, development methods, development productivity, DevOps, and engineer careers. On the day, all the sessions were virtually packed, and with those and the sponsor booths combined, the whole event was really bustling. https://event.shoeisha.jp/devsumi/20240918 Osaka Tech Lab’s First Time a Sponsor Our company participated in the event by setting up a sponsor booth and taking the podium for a sponsor session. Starting this fiscal year, KINTO Technologies has been sponsoring a variety of events, but this was Osaka Tech Lab’s first foray into sponsoring an event (that’s how we call KINTO Technologies’ Osaka office.) So we needed to think about: What kind of booth to set up The novelty items to prepare How to guide attendees at the booth To that end, we had meticulous discussions to make sure we were fully prepared in time for the big day. The Sponsor Booth Here is our completed booth! I would like to take this opportunity to touch on a few items in particular. ![KINTO Technologies’ completed sponsor booth](/assets/blog/authors/high-g/20241030/img4.jpg =512x) The Osaka Tech Lab Seen through Data First, I will talk about the board with all the graphs and figures on it in the center of the image. This is a visual representation of information gleaned from talking to Osaka Tech Lab’s members. An interesting feature is how the overall design mimics the parts of a plastic model kit! Our company’s designers created it quickly, but the final product is very polished. ![Board with Osaka Tech Lab represented visually through graphs](/assets/blog/authors/high-g/20241030/img5.jpg =512x) Survey board Next is the Survey Board on the right of the photo. This is a board for getting survey responses from booth visitors. We gave each visitor a sticker and asked them to place it on the job categories on the board that applied to them, helping visualize the IT domains they belong to. ![Board for a sticker survey](/assets/blog/authors/high-g/20241030/img6.jpg =512x) Survey Results The survey results revealed that many visitors are somewhat familiar with KINTO and KINTO Technologies, but there are still numerous target groups that we need to engage with Regarding occupations, many people placed stickers between 'back-end' and 'front-end.' While the nature of the event may have influenced this, it also highlighted that many engineers in Kansai tend to be more generalists than specialists. I believe we gathered valuable data to help expand awareness of the Osaka Tech Lab Thank you to everyone who cooperated with our survey board! ![Survey results](/assets/blog/authors/high-g/20241030/img7.jpg =512x) Kumobii Stuffed Toy There is a white, fluffy toy sitting on the left in the photo. This is KINTO’s official mascot character, Kumobii . Absolutely adorable, right? It was a huge hit with booth visitors, and Findy and KIKKAKE even posted it on their X accounts. Thank you so much! ![Twitter image: Findy](/assets/blog/authors/high-g/20241030/img8.jpg =512x) ![Twitter image: Kikkake](/assets/blog/authors/high-g/20241030/img9.jpg =512x) Novelty Item On the day, we gave the first 100 visitors to our booth a freebie multi-card tool (a tool set consisting of an aluminum card that can serve multiple roles). We also handed out Kumobii-shaped clips made of paper to people who came along to our sponsor session. ![Multi-card tool](/assets/blog/authors/high-g/20241030/img10.jpg =512x) ![Kumobii paper clip](/assets/blog/authors/high-g/20241030/img11.jpg =512x) Sponsor Session The speaker was Okita-san, who has been a member of the Osaka Tech Lab since it was established. The session theme was “Pursuing a Dual Career as a Project Manager and a Mobile App Engineer to Create the Future of Mobility from Osaka!: Challenges the Osaka Tech Lab and I Get to Take On in the Toyota Group.” The following is an overview of the session. An introduction to Okita-san’s career to date Carving out a career as a project manager who doubles as a mobile app engineer, something unprecedented in the company Sowing seeds for launching, developing, and operating products at Osaka Tech Lab The phrase “never forget your passion” left a big impression on me, and it struck me that the career Okita-san currently has is the fruit of always having soldiered on instead of just giving up and quitting, even when motivation was low. Closing Thoughts This was our first time taking part in Dev Summit KANSAI, and I was truly amazed at how many people were there despite it being a weekday. I got to go along to some of the sessions myself in between helping out with our booth, and I learned a lot, got lots of ideas for my next steps, and enjoyed the whole event very much indeed. Large-scale conferences that encompass a wide range of technical fields are rare in Kansai, so I hope this level of passion continues indefinitely. I would like to express my gratitude to all the staff involved in running Dev Summit KANSAI. Thank you very much.
アバター
This article is the second post in the KINTO Technologies Advent Calendar 2024 . Introduction to the background of our activities Hello. I'm “Kinchan” from the Manabi-no-Michi-no-Eki team. I usually work as a corporate engineer, maintaining and managing the IT system used by our entire company. Recently, I have been involved in various activities, including as a member of organizations such as the "Generative AI Utilization Project" and the "Technical Public Relations Group." In a previous tech blog , I introduced the origin of the "Manabi-no-Michi-no-Eki." It also described our "in-house podcasting" activities. In this article, I will provide a detailed introduction to our podcast initiative. This podcast initiative began with the following idea from HOKA-san, a member of the Manabi-no-Michi-no-Eki: I want to share various in-house activities through podcasts! In addition to HOKA-san's very simple motivation, our team was made up of members who basically never said “No,” which led us to pursue it as part of the Manabi-no-Michi-no-Eki team activities. Of course, since this is an initiative of the Manabi-no-Michi-no-Eki team, it is important to effectively combine it with “information dissemination for learning purposes.” After some discussions, we arrived at the following conclusion. Conducting interviews with persons who are hosting, managing, or participating in in-house study groups. And disseminating the interview results. Our purposes were as follows: To answer the question, "There are many study groups in the company, but who are organizing them and what motivates them?" To let our audience hear first-hand voices from those who have actually participated in in-house study groups and feel their value. If someone is interested, this could be an opportunity for them to join a study group. By making the existence of various study groups visualizing/audible, a "culture of learning" will take root within the company. "What is a podcast?" Even though we have decided to start podcasting, we had questions like, "What should we do to make a podcast?" Is it something that can be released in the form of Internet radio? (Will people outside the company listen to it?) Does it have to be distributed using a dedicated app? (This could involve preparation efforts by both the distributor and the listeners...) What kind of equipment is required? The more we thought about it, the more obstacles we found. That said, our goal is simply to "deliver in-house information to the rest of the company," so we decided to proceed with an "agile mindset" of "first using an available system to make a prototype to test out the feel," and then "receiving feedback and making kaizen. Now it's time for the first interview! Once that's decided, it's time to choose the study group to interview! It just so happened that a large-scale study session called a "joint study session" was scheduled within the company, so we decided to attend this study session first and conduct interviews with its management team members. However, on the day of the study session, things got hectic, and we were unable to record the interview. As a result, we decided to take an alternative approach by having the management team members gather at a later date for an interview. (In fact, this alternative approach became the model for our future podcasts.) The actual interview was very exciting, and we were able to complete the recording successfully. However, we then faced a new challenge! In fact, we had reached this point without deciding how to create the podcast content or how to distribute it. After much discussion and trial and error within our team, we settled on the following: Processing of the recorded data Using Clipchamp (a Microsoft 365 family product) to create a video file with a focus on audio. Streaming method The file is uploaded to the in-house SharePoint, where the audience can access and play it on their PC or smartphone. The podcast release flow was finally decided as follows: Completing the content Conducting an internal review within the Manabi-no-Michi-no-Eki team Conducting a review by the interviewees Conducting a supervisor review (after the first review, if there are no major concerns, the supervisor will hand over the decision-making responsibility to us for subsequent reviews.) In-house announcement (= the release) We have established this simple flow, suitable for in-house content only. After that, once all the checks passed, we were able to finally release it inside the company! ![](/assets/blog/authors/ktc-taku-yajima/2024-12-02/started-a-podcast001.png =700x) Subsequent podcasts Having gained confidence from the release of our first podcast, we are now moving forward with the planning of our second and third podcasts. With each interview, we accumulated knowledge and became able to formalize our process to a certain extent. Formalization of the planning process Collecting information on in-house study sessions and information dissemination activities The Manabi-no-Michi-no-Eki team will approach the management team members to conduct interviews. Podcasting the interview results Internal announcement after reviews by the management team and interviewees Formalization of recording, editing, and distribution Interviews will be conducted via Zoom (since our members are dispersed across multiple locations). Each person's PC microphone and conference room microphone will be utilized. The sound quality will be shelved for the time being. Zoom recording data will be used as the audio source. The audio source data will be edited and processed using Clipchamp. After editing the audio source data, it will be saved in the company's internal storage (MS SharePoint). Listeners will access the content via MS Stream. Thanks to this formalized process, we have been able to smoothly operate the system, and up to now, we have successfully distributed 11 pieces of content. About the future As we continue our podcasting activities, we have developed a desire to expand in various ways. We want to interview people who are involved in various in-house activities beyond study group members. We also want to interview members of the management and have them share their candid opinions with the rest of the company. We want to expand our output beyond just the company. We, the Manabi-no-Michi-no-Eki team, will continue to actively share information in the coming years. Stay tuned!
アバター
Introduction I am Okapi, from the Quality Assurance Group. Since I often participate in projects as the primary person in charge of QA, in this article, I'd like to share how QA team participates in projects, communicates with the development team, and progresses in its testing at KINTO Technologies. Purpose of This Article When proceeding a project with a team that has never worked with QA before, there is often some trial and error involved as they try to understand what QA can contribute and how the process will unfold. To ensure smoother progress in such cases, I aim to raise awareness of QA. What is QA? QA is an acronym for "Quality Assurance." "Quality Assurance" is a broad term that involves ensuring quality in various aspects. We conduct testing from the user's perspective, by focusing on scenario testing based on actual user usage assumptions and verifying screen UI to prevent any inconvenience and ensure usability. The primary roles of the QA and development teams in testing are outlined in the table below. | Item | QA | Development | Remarks | | ---- | ---- | ---- | ---- | | Verify specifications against system requirements | ㅤ◎ㅤㅤ | ㅤ〇ㅤㅤ| QA ensures that functions and performance meet system requirements, based on user's perspective. | |Verify according to scenarios of user use | ㅤ◎ㅤㅤ | ㅤ△ㅤㅤ| Mainly verified by QA | | Other than the above | ㅤ△ㅤ ㅤ| ㅤ◎ㅤㅤ| When requested, the development team's external integration testing and QA will verify within feasible resource limits | Overview of QA testing Phase Summary Test Plan Phase Share overall project schedule and specification documents (system requirements and screen specifications) to allow QA to create a test plan detailing how the subsequent phases will proceed. Test Analysis Phase Based on specification documents (system requirements and screen specifications), create test perspectives that clarify the scope of testing (to be tested/not to be tested). Test Design Phase Create test cases (prerequisites, procedures, and expectations) based on the test perspectives. Test Implementation Phase Conduct tests based on the created test cases, report defects, and verify modifications. Points Requiring Communication with the Development Team Test Plan Phase Ensure alignment with the development team based on the test plan, which outlines the QA testing period, verification environment, person in charge of conducting QA testing, development team contact, and target devices/browsers. Test Analysis Phase Use JIRA or Confluence to ask questions about the information necessary to create test perspectives. For areas with differing understandings or multiple development team contacts, hold meetings to confirm and organize the specifications. Once the specifications are organized, create test perspectives that clearly define the test scope (to be tested/not to be tested) and confirm alignment with the development team. For the test scope, from a black box testing perspective, the target is on areas users will actually interact with (as deemed necessary by QA for the project), while excluding areas users do not access, such as system administration screens. However, since scenario testing involves verifying the sequence of flow, areas that intersect with the user-side testing, even if typically excluded, are included as test targets. *For regression testing, where quality is assured, the test scope is adjusted based on the shared schedule and resources. Test Design Phase The procedure for conducting tests will be reviewed, but test cases are created based on the agreed-upon test perspectives. Therefore, alignment with the development team is basically not conducted at this phase. Test Implementation Phase Issues (expected results differing from specifications), questions (missing or unclear specifications), and improvement requests (aligned with specifications but unclear to users) are reported in JIRA. Once addressed by the development team (fixes), the QA team conducts re-verification. Testing is considered complete when all test cases and the JIRA tickets are addressed, or when the remaining JIRA tickets are excluded from the current QA test scope. Once implementation is complete, we participate in the overall retrospective (e.g., KPT analysis) to discuss areas for improvement and apply them to future projects. Future Challenges Depending on the project or product, "how to organize the documents" may differ, so after aligning on specifications in JIRA or Confluence, QA organizes and documents the overall user-related system specifications and workflows before moving forward. However, depending on the system's scale, this can be time-consuming. By organizing documents and processes within the QA team and ensuring smooth communication with the development team, we aim to efficiently summarize specifications for accurate understanding. Conclusion The QA team is an independent organization, which may give the development team the impression that they are requesting tasks to QA or that QA is simply conducting tests. However, as part of KINTO Technologies, we see ourselves as partners working together to create high-quality systems. Therefore, we aim to continue fostering a collaborative relationship.
アバター
KINTOテクノロジーズで my route(iOS) を開発しているRyommです。 my routeアプリのライブラリ管理ツールがついに!CocoaPodsからSwift Package Manager(以下SPM)に移行しました! はじめに my routeではCocoaPodsを使用していました。 しかし2024年夏ごろ、CocoaPodsから以下のお知らせが発布されました。 https://blog.cocoapods.org/CocoaPods-Support-Plans/ CocoaPodsがメンテナンスモードに移行するというお知らせです。 使えなくなるわけではありませんし、セキュリティの問題などには対応してくれます。ただし、GitHub issueにおけるサポートや新機能の追加などは行われなくなります。 そうすると今後、新しいライブラリを入れたい時にCocoaPodsがサポートされなくなっていたり、CocoaPods自体に何か問題が起こっても直すことができない、といった場面が起こり得ます。 以前からmy routeではSPMへの移行の話は持ち上がっていましたが、このCocoaPodsのお知らせが追い風となり、満を持してSPMへの移行に着手しました。 Swift Package Managerとは SPMとは、Appleが提供する純正ライブラリ管理ツールです。 https://github.com/swiftlang/swift-package-manager iOSでライブラリ管理ツールといえば、他にはCocoaPodsやMintなどのサードパーティツールがあります。これらと最も大きく違うのは、Xcodeに統合されているという点です。 これにより、必要なタイミングで Package.resolved を更新するだけで、プロジェクトファイルを開いた時やビルド時に最新の指定バージョンのライブラリを取り込んでくれます。 バージョン更新をするたびにチームへ pod install を実行してくれ〜と声をかける必要がなくなるのです。 移行したよ ひたすら移していくのみですが、いくつか躓いたポイントや工夫点があるので紹介します。 指定したバージョンのライブラリが降ってこない 使用するバージョンを変更する際、Xcodeで「Reset Package Caches」や「Update to Latest Package Versions」を実行しても、Package.swiftで指定しているバージョンが降ってこない問題がありました。DerivedDataを消しても直りません。 これは、 ~/Library/Caches/org.swift.swiftpm や ~/Library/org.swift.swiftpm などに深淵のキャッシュが残っているためです。これらを消すと正しいバージョンのライブラリが降ってくるようになります。 テストの時だけビルドが通らない my routeは複数のスキーマを持っており、それぞれ依存するライブラリが少し異なります。 そのため以下のようにプロジェクト自体のPackage.swiftとは分離した構成になっています。 プロジェクトファイルと同じワークスペース配下にDependenciesパッケージを作成し、このパッケージ内で各スキーマと1対1になるproduct(フレームワーク)となるようにライブラリを管理しています。 これらのproductを右側のスクリーンショットのように、それぞれのターゲットの Frameworks, Libraries, and Embedded Content にて紐付けています。 しかしこの構成にしていると、 xcodebuild build は通りますが、 xcodebuild build-for-testing が通らないという問題が起こりました。XcodeのGUI上ではRunとTestに対応するコマンドです。 本来、パッケージのものはパッケージ内でテストします。 しかし上記の構成では、プロジェクト本体のメインのターゲットでテストを実行します。つまり、パッケージの外でテストしているということです。 ということはこれは...違法建築...なのですが、ゆくゆくは同じパッケージ内にテストを詰めるように直していくということで、一旦構成は維持したままテストできるようにします。 RunとTestではLinkerあたりの挙動が異なるらしいことが原因でした。 Runのビルド生成物を①、Testのビルド生成物を②とします。 ①にはDependenciesのSPMが含まれていますが、②には含まれていません。 そのため、MainTarget内のテストがDependenciesのライブラリに依存しているとビルドに失敗してしまいます。 簡単な解決策としては、MainTarget内のSPMに持たせるとRunでもTestでもビルドに含まれるため、そちらにテストで必要なライブラリを移します。 根本的に解決するには、先述のように1つのパッケージ内でテストが簡潔するようにすると良いでしょう。 SwiftLintやLicensePlistをSPMに乗せる SwiftLintやLicensePlistはプロジェクトのBuild Phaseに含めてビルド時に実行されるようにしたいため、workspaceから独立した場所に別のパッケージを作成します。 Project/ ├── Test.xcworkspace ├── Test.xcodeproj ├── Test/ │ └── ... ├── ... └── tools // <- this! ├── Package.swift └── Package.resolved 新しく作成したtoolsパッケージにSwiftLintやLicensePlistなどBuild Phaseに含めたいライブラリを入れます。 そして以下のようなシェルを用意し、初回だけ実行してローカルにライブラリを落としておきます。 SPM_PACKAGE_PATH=tools SPM_BUILD_PATH=tools/.build/release echo "swiftlint" && $SPM_BUILD_PATH/swiftlint --version || swift run -c release --package-path $SPM_PACKAGE_PATH swiftlint --version echo "license-plist" && $SPM_BUILD_PATH/license-plist --version || swift run -c release --package-path $SPM_PACKAGE_PATH license-plist --version すると tools/.build/release/swiftlint で実行可能になるので、これをBuild Phaseに入れます。 LicensePlistも同様です。 Bitriseなどから実行する際も、シェルを実行してからプロジェクトをビルドすると実行されることが確認できます。 おわりに 移行してから数ヶ月経ち、まだまだいくつかの問題もありますが、ライブラリの入れ替えは楽になったと思います。 またライブラリ起因の問題かどうか判断したい時に、アプリ版のPraygroundsでサクッと確認できることが個人的には楽に感じます。 SPMがプロジェクトで使えるようになったことで、SPMでしか提供されていないApple製ライブラリも使えるようにもなりました。 今後はこれらのライブラリも使ってより良い実装にしていきたいです。
アバター
はじめに こんにちは、クラウドインフラグループの松尾です。 私たちクラウドインフラグループでは、社内全体のインフラ領域を設計から運用まで幅広く担当しています。 今回は、re:Inventで発表された AWS Transfer Family ウェブアプリ についてご紹介します。 https://aws.amazon.com/jp/about-aws/whats-new/2024/12/aws-transfer-family-web-apps/ 本記事では、ウェブアプリの概要、作成手順、設定方法、そして実際に使用してみた感想をお伝えします。参考になれば幸いです。 概要 AWS Transfer Family ウェブアプリ とは、簡単に言うと、ブラウザ上でS3バケット内のファイルを操作できるアプリケーションを、コードを書くことなく作成できるサービスです。 従来、GUIでS3にアクセスするにはAWSコンソールやWinSCPなどの専用ソフトウェアが必要で、設定も必要でした。 そのため、非技術者や一時的なユーザーには操作が難しいという課題がありました。 AWS Transfer Family ウェブアプリは、直感的なインターフェースで、技術的な知識がなくても簡単に操作できる点が特徴です。 さらに、IAM Identity CenterやS3 Access Grantsと統合されているため、細かいアクセス制御も可能です。 料金について AWS Transfer Family ウェブアプリの料金は、ユニット数(1ユニットで5分間に最大250セッション)に基づく1時間単位の従量課金制です。 リージョン 料金 東京 ユニット数*0.5 USD/時間 例えば、1ユニットを24時間フル稼働させると 12USD/日 、1か月では 360USD となり、 長時間の利用ではかなりのコストがかかります。 手軽なサービスの割には高額と言えますね。 利用ケースによっては過剰なコストとなる可能性があるため、用途に応じたサービスの利用が大事ですね。 Webアプリの作成方法 それでは早速アプリを作成してみましょう。 (執筆時点ではTerraform未対応のため、AWS Management Consoleを使用します。) AWS Management Consoleにログインし、AWS Transfer Familyのナビゲーションペインから「ウェブアプリ」を選択し、ウェブアプリを作成をクリック。 今回はNameタグのみ設定して「次へ」をクリック。 ※ IAM Identity Centerの設定が事前に必要 です。未設定の場合、先に準備してください。 ページタイトル、ロゴ、ファビコンを設定し、「作成」をクリック。 今回、ロゴ・ファビコンは設定しないでいきます。 これでウェブアプリの作成は完了です! 簡単ですね! ユーザー/グループとS3 Access Grantsの設定 次に、ウェブアプリにグループの割り当てとS3 Access Grantsを設定していきます。 グループの割り当て ウェブアプリ詳細画面で「ユーザーとグループの割り当て」をクリック。 今回はグループを用意したので、既存のグループを選択して割り当てます。 S3 Access Grantsの設定 次にAccess Grantsの設定です。ロケーションと権限の2つを設定します。 S3のナビゲーションペインからAccess Gratsに移動すると、「権限」 「ロケーション」タブがあるのでここから設定していきます。 ロケーションから設定します。 ロケーションではアクセス対象のS3バケット及び、S3バケットに対する権限を割り当てたIAMロールを指定します。 今回は検証のためAWS管理ポリシー"AmazonS3FullAccess"を付与してます。 権限では先ほど設定したロケーションを指定し、サブプレフィックス、許可、被付与者タイプ、IAM プリンシパルタイプ、IAM プリンシパルユーザーを設定し、権限を作成します。 IAM Identity Centerで認証を行うので、画像の通り、設定しています。 今回は設定していませんが、オプションとして、アプリケーション単位で制限をすることも出来るようです。 CORSの設定 バケットの中身を参照できるように対象のS3バケットで Cross-Origin Resource Sharing (CORS) からCORS設定を追加します。 今回は以下の内容を設定しました。 [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "PUT", "POST", "DELETE", "HEAD" ], "AllowedOrigins": [ "https://${ウェブアプリID}.transfer-webapp.ap-northeast-1.on.aws" ], "ExposeHeaders": [ "Access-Control-Allow-Origin" ] } ] 試してみる ここまで出来たら準備完了です。 ウェブアプリのURLにアクセスし、認証を行ったらS3の管理画面が出てきました! ロケーションで設定したS3バケットが表示されていることが分かります。 それじゃあ実際に今回は対象バケットにs3ファイルの削除をしてみましょう。 Folderのリンクからオブジェクトを操作しようとしましたが、あれ?権限が不足してるようですね。 どうやらAccess Grantsのロケーションで設定したIAMロールでsts:SetContextの設定が必要だったようです。 User Guide にも記載がありました。 設定を追加して再度試したところ、無事オブジェクトの確認が出来ました。 ちなみにCORSの設定ができていないとここでNetwork Errorが出てきます それじゃあ、ここから本当にオブジェクトを選択して、削除をしてみます。 操作も簡単です。 削除したいオブジェクト(今回はテスト1.txt)を選択して三点リーダーからDeleteを選択するだけです。 Webアプリ上から削除されてますね。 コンソール上からも同様に削除できてることを確認しました。 感想 IAM Identity CenterやAccess Grantsの設定が必要でしたが、ノーコードでS3の管理アプリケーションを簡単に構築できる点は非常に便利だと感じました。 設定箇所が多いため、戸惑った部分も少しありましたが、 IaCに対応することで、さらに使いやすくなると思います。 また、本サービスは同じくre:Inventで発表された Storage Browser for Amazon S3 を基にしているため、 プログラムスキルがある方はそちらのサービスを利用することで独自のカスタマイズも可能です。 https://aws.amazon.com/jp/about-aws/whats-new/2024/12/storage-browser-amazon-s3/ このブログが少しでも参考になれば幸いです。
アバター
はじめに こんにちは。KINTOテクノロジーズ(以下KTC) プラットフォームグループ Platform Engineeringチームで内製ツールの開発・運用をおこなっている山田です。 Platform Engineeringチームが開発するCMDBについて詳しく知りたい方は以下の記事をご覧ください! https://blog.kinto-technologies.com/posts/2023-12-14-CMDB/ 今回はCMDBの機能の一つであるチャットボットに、 生成AIとText-to-SQL を活用したCMDBのデータ検索機能とcsv出力機能を実装したお話をしたいと思います。 CMDBのチャットボットではCMDBの利用方法の質問やCMDBで管理されたデータに関する質問が可能です。 CMDBのデータに関する質問は、元々はChromaDBを使ったRAGの仕組みで質問に対する回答を生成していましたが、以下の理由からText-to-SQLでの実装に移行しました。 RAGと比較したText-to-SQLのメリット データの正確性と即時性 CMDBのデータベースから直接最新データをリアルタイムで取得が可能 データの更新に追加の処理が不要 システムの簡素化 ベクトルDBやEmbedding処理のためのインフラが不要(ChromaDBとEmbeddingしたデータの追加用バッチが不要になった) これらの理由から、CMDBのような構造化されたデータを扱うシステムにおいては、Text-to-SQLの方が適していると判断しました。 Text-to-SQL とは Text-to-SQLは自然言語のクエリからSQLクエリに変換する技術のことです。そのためSQLの知識を持たないユーザーでもデータベースから必要な情報を簡単に抽出することが可能になります。 これにより、CMDBのデータベースで管理しているプロダクト、ドメイン、チーム、ユーザー、ECR, VMDR等の脆弱性情報などのデータを自然言語のクエリから取得できるようになります。 KTC内で活用できそうな例だと、 適切な管理がされていないドメイン(CMDBでプロダクトと紐づいていないドメイン)一覧を取得 全社員の Atlassian ID の取得 MSP(Managed Service Provider)チームがPCの脆弱性対応などの依頼を、該当者をメンションした形でチケット作成するため グループごとに担当するプロダクトの関連リソースに検出された脆弱性数の集計 AWSリソースの起動停止スケジュールが設定されていないプロダクトの抽出 以前までだとこのようなデータの抽出依頼がPlatform Engineeringチームに来た際、担当者がCMDBのデータベースから直接SQLクエリを実行し、データを抽出、加工して依頼者にデータを渡していました。 依頼者がCMDBのチャットボットでText-to-SQLを使ってデータを抽出することができるようになると、以下の図のようにわざわざ担当者に依頼をしなくても簡単にデータの抽出ができるようになります。 Text-to-SQLは便利な機能ですが、安全でないSQL生成のリスクに気を付けなければなりません。 以下の図の例は極端なケースですが、自然言語からSQLが生成されるため意図せずにデータの更新や削除、テーブル構造の変更をするSQL文が生成される恐れがあります。 そのため以下の方法で安全でないSQLの生成を回避する必要があります。 Read OnlyのDBエンドポイントに接続する DBのユーザーを参照権限のみに設定する アプリケーションの実装で SELECT 以外のコマンドは実行しないようバリデーションチェックをおこなう システム構成 こちらがCMDBのアーキテクチャになります。今回のお話に関係のないリソースは除外してあります。 最初に説明した通り、元々ベクトルDBとしてChromaDB、CMDBの使い方に関する情報をConfluenceから取得(LlamaIndexで実装)、CMDBのデータをデータベースから取得(Spring AIで実装)してChromaDBに投入していました。 今回はCMDBのデータに関する質問の回答を Spring AI + ChromaDB でのRAG機能から、Text-to-SQLを使った機能に移行しました。 Text-to-SQLの実装 ここからは実際のコードをお見せしながら実装内容を説明したいと思います。 CMDBデータの検索機能 スキーマ情報を取得 まずはLLMにSQLを生成させるときに必要なスキーマ情報を取得します。 スキーマ情報は少ない方が精度は上がるため、必要なテーブルのみ指定する方法にしました。 また、LLMがSQL文を生成するときの判断材料としてテーブルのカラムのコメントが重要になるため、事前に全て追加しておく必要があります。 def fetch_db_schema(): cmdb_tables=['table1', 'table2', ...] cmdb_tables_str = ', '.join([f"'{table}'" for table in cmdb_tables]) query = f""" SELECT t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_COMMENT, c.COLUMN_NAME, c.DATA_TYPE, c.COLUMN_KEY, c.COLUMN_COMMENT FROM information_schema.COLUMNS c INNER JOIN information_schema.TABLES t ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME WHERE t.TABLE_SCHEMA = 'cmdb' AND t.TABLE_NAME IN ({cmdb_tables_str}) ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME, c.COLUMN_NAME """ connection = get_db_connection() try: cursor = connection.cursor() cursor.execute(query) return cursor.fetchall() finally: cursor.close() connection.close() 取得結果例 TABLE_SCHEMA TABLE_NAME TABLE_COMMENT COLUMN_NAME DATA_TYPE COLUMN_KEY COLUMN_COMMENT cmdb product プロダクトテーブル product_id bigint PRI プロダクトID cmdb product プロダクトテーブル product_name varchar プロダクト名 cmdb product プロダクトテーブル group_id varchar プロダクトの担当部署(グループ)ID cmdb product プロダクトテーブル delete_flag bit 論理削除フラグ 1=削除, 0=未削除 取得したスキーマ情報をLLMに渡すプロンプト用のテキストにフォーマットする def format_schema(schema_data): schema_str = '' for row in schema_data: schema_str += f"Schema: {row[0]}, Table Name: {row[1]}, Table Comment: {row[2]}, Column Name: {row[3]}, Data Type: {row[4]}, Primary Key: {'yes' if row[5] == 'PRI' else 'no'}, Column Comment: {row[6]}\n" return schema_str カラムごとに以下のようなテキストに変換してLLMにスキーマ情報を渡します。 Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: product_id, Data Type: bigint, Primary Key: PRI, Column Comment: プロダクトID Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: product_name, Data Type: varchar, Primary Key: no, Column Comment: プロダクト名 Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: group_id, Data Type: varchar, Primary Key: no, Column Comment: プロダクトの担当部署(グループ)ID Schema: cmdb, Table Name: product, Table Comment: プロダクトテーブル, Column Name: delete_flag, Data Type: bit, Primary Key: no, Column Comment: 論理削除フラグ 1=削除, 0=未削除 CMDBのチャットボットからの質問とスキーマ情報からLLMでSQLクエリを生成する ここが自然言語からSQLクエリを生成するText-to-SQLの部分です。 質問内容とスキーマ情報をもとにプロンプトで様々な条件を指定してLLMにSQLを生成させています。 例えば、 MySQL:8.0 で有効なクエリを生成する ID以外の条件式はあいまい検索にする 基本的に論理削除されたデータは検索対象外とする SQL文以外は生成しない コンテキスト情報の追加 「KTCの~」、「CMDBの~」という質問は「全ての~」に変換する リージョンに関する質問はAWSのリージョンに変換する 東京リージョン -> ap-northeast-1 に変換 などがあり、特に「SQL文以外は生成しない」の指示が重要で、これがうまく伝わらないと 「頂いた情報をもとに次のSQLを生成しました。SELECT ~」 このような不要なテキストも含めた回答になってしまうことがよくありました。 したがって不要なテキスト、説明、マークダウン形式などが生成されない、「SELECT ~」のみのSQL文が生成されるようなプロンプトが必要です。 def generate_sql(schema_str, query): prompt = f""" Generate a SQL query based on the given MySQL database schema, system contexts, and question. Follow these rules strictly: 1. Use MySQL 8.0 syntax. 2. Use `schema_name.table_name` format for all table references. 3. For WHERE clauses: - Primarily use name fields for conditions, not ID fields - Use LIKE '%value%' for non-ID fields (fuzzy search) - Use exact matching for ID fields - Include "delete_flag = 0" for normal searches - Use "delete_flag = 1" only when the question specifically asks for "deleted" items CRITICAL INSTRUCTIONS: - Output MUST contain ONLY valid SQL query. - DO NOT include any explanations, comments, or additional text. - DO NOT use markdown formatting. - DO NOT generate invalid SQL query. Process: 1. Carefully review and understand the schema. 2. Generate the SQL query using ONLY existing tables and columns. 3. Double-check query against schema for validity. System Contexts: - Company: KINTO Technologies Corporation (KTC) - System: Configuration Management Database (CMDB) - Regions: AWS Regions (e.g., Tokyo region = ap-northeast-1) Interpretation Rules: - "KTC" or "CMDB" in query: Refer to all information in the database Examples: "Employees in KTC" -> "All users" "KTC's products" -> "All products" "Domains on CMDB" -> "All domains" - Region mentions: Interpret as AWS Regions Example: "ECR repositories in Tokyo region" -> "ECR repositories in ap-northeast-1" Database Schema: {schema_str} Question: {query} """ return llm.complete(prompt).text.strip() LLM, Text-to-SQLで生成したSQLに対して、 SELECT 文のみ許可するようバリデーションチェックをおこなう 安全でないSQL生成リスクの対策としてRead OnlyのDBエンドポイントに接続していますが、クエリ以外のSQLが生成されていないか確認をします。 LLMで生成されたSQLクエリを実行する LLMで生成されたSQLクエリとSQLの実行結果、質問内容からLLMで回答を生成する 最後に実行したSQLクエリ、SQLの実行結果、質問内容をLLMに渡して回答を生成します。 ここではText-to-SQLのときの指示が多いプロンプトとは違い、多くの指示はしていないですが、DBスキーマの構成や物理名は回答に含めないような指示は追加しています。 def generate_answer(executed_sql, sql_result, query): prompt = f""" Generate an answer based on the provided executed SQL, its result, and the question. Ensure the answer does not include information about the database schema or the column names. Respond in the same language as the question. Executed SQL: {executed_sql} SQL Result: {sql_result} Question: {query} """ return llm.stream_complete(prompt) 実行結果 質問:プラットフォームグループのプロダクトを教えて この質問とデータベーススキーマからLLMが以下のようなSQLを生成します。 SELECT product_name FROM product WHERE group_name LIKE '%プラットフォーム%' AND delete_flag = 0; そして、これらの情報とSQLの実行結果をLLMに渡して回答を生成しています。 これはECRスキャン結果の脆弱性情報の取得結果です。 CMDBデータのcsvファイル出力機能 スキーマ情報を取得 取得したスキーマ情報をLLMに渡すプロンプト用のテキストにフォーマットする ここまではCMDBデータの検索機能と同じです。 CMDBのチャットボットからの出力依頼とスキーマ情報から、LLMでSQLクエリを含むJSONオブジェクトを生成する ここでCMDBのデータをcsvとして出力したい内容の自然言語から、LLMを使って出力時のカラム名とそれを検索するためのSQL文のJSONオブジェクトを生成させます。 条件の内容は基本的にCMDBデータの検索機能のプロンプトと同じですが、テンプレート通りのJSONオブジェクトを生成させるための指示を強調しています。 以下がそのプロンプトです。 prompt = f""" Generate a SQL query and column names based on the given MySQL database schema, system contexts and question. Follow these rules strictly: 1. Use MySQL 8.0 syntax. 2. Use `schema_name.table_name` format for all table references. 3. For WHERE clauses: - Primarily use name fields for conditions, not ID fields - Use LIKE '%value%' for non-ID fields (fuzzy search) - Use exact matching for ID fields - Include "delete_flag = 0" for normal searches - Use "delete_flag = 1" only when the question specifically asks for "deleted" items Process: 1. Carefully review and understand the schema. 2. Generate the SQL query using ONLY existing tables and columns. 3. Extract the column names from the query. 4. Double-check query against schema for validity. System Contexts: - Company: KINTO Technologies Corporation (KTC) - System: Configuration Management Database (CMDB) - Regions: AWS Regions (e.g., Tokyo region = ap-northeast-1) Interpretation Rules: - "KTC" or "CMDB" in query: Refer to all information in the database Examples: "Employees in KTC" -> "All users" "KTC's products" -> "All products" "Domains on CMDB" -> "All domains" - Region mentions: Interpret as AWS Regions Example: "ECR repositories in Tokyo region" -> "ECR repositories in ap-northeast-1" Output Format: Respond ONLY with a JSON object containing the SQL query and column names: {{ "sql_query": "SELECT t.column1, t.column2, t.column3 FROM schema_name.table_name t WHERE condition;", "column_names": ["column1", "column2", "column3"] }} CRITICAL INSTRUCTIONS: - Output MUST contain ONLY the JSON object specified above. - DO NOT include any explanations, comments, or additional text. - DO NOT use markdown formatting. Ensure: - "sql_query" contains only valid SQL syntax. - "column_names" array exactly matches the columns in the SQL query. Database Schema: {schema_str} Question: {query} """ LLM, Text-to-SQLで生成したSQLに対して、 SELECT 文のみ許可するようバリデーションチェックをおこなう LLMで生成されたSQLクエリを実行する ここもCMDBデータの検索機能と同じです。 実行結果を使ってcsvファイルを出力する LLMに生成してもらったSQLの結果とカラム名を使ってcsvファイルの出力をします。 column_names = response_json["column_names"] # LLMで生成したJSONオブジェクトからカラム名を取得 sql_result = execute_sql(response_json["sql_query"]) # LLMで生成したSQLの実行結果 csv_file_name = "output.csv" with open(csv_file_name, mode="w", newline="", encoding="utf-8-sig") as file: writer = csv.writer(file) writer.writerow(column_names) writer.writerows(sql_result) return FileResponse( csv_file_name, media_type="text/csv", headers={"Content-Disposition": 'attachment; filename="output.csv"'} ) 実行結果 出力したい内容とカラムを指定してチャットに投げると、以下の流れでcsvファイルを出力できるようになりました。 まず、チャットでのメッセージとデータベーススキーマからLLMが以下のようなJSONオブジェクトを作成します。 { "sql_query": "SELECT service_name, group_name, repo_name, region, critical, high, total FROM ecr_scan_report WHERE delete_flag = 0;", "column_names": ["プロダクト名", "部署名", "リポジトリ名", "リージョン名", "critical", "high", "total"] } これらの情報をもとにSQLを実行して、csvファイルを出力する流れです。 プロダクト名 部署名 リポジトリ名 リージョン名 critical high total CMDB プラットフォーム ××××× ap-northeast-1 1 2 3 CMDB プラットフォーム ××××× ap-northeast-1 1 1 2 CMDB プラットフォーム ××××× ap-northeast-1 1 1 2 次のステップ ここまで生成AIとText-to-SQLを活用して、CMDBデータの検索機能とcsvデータ出力機能を実装しましたが、以下のようにまだまだ改善の余地があります。 CMDBデータの検索機能では2回LLMを呼び出しているため、速度が遅い 複雑、あいまいな質問に弱い 自然言語は本質的にあいまいなので、質問の内容に対して複数の解釈ができてしまう スキーマの正確な理解 スキーマ情報は複雑で、テーブル間のカラムの関係を理解させるのが大変 コンテキスト情報の追加 現状は最初のプロンプトで最低限のコンテキスト情報を追加しています。今後、より多くのコンテキスト情報を追加するとなると、最初のLLM呼び出しの前に質問内容を大量のコンテキスト情報から適切な質問に変換する処理方法や、fine-tuningでKTC特有のコンテキスト情報を含むデータセットで追加学習させる方法を検討しています Query Routingの実装 フロントエンドから呼び出すAPIはCMDBのデータ検索とcsv出力で2つ分かれているため、APIは1つに統一して、質問の内容からどちらのAPIを呼び出すべきか判断できるように改善したい さいごに 今回は生成AIとText-to-SQLを活用したCMDBのデータ検索機能とcsv出力機能についてお話ししました。 生成AI関連の技術は日々新しいものが出続けているためキャッチアップが大変ですが、今後はこれまで以上にアプリケーション開発にAIが絡んでくるため、興味のあるものや社内のプロダクトに適用できそうなものは積極的に活用していきたいと思います。
アバター
はじめに こんにちは! KINTOテクノロジーズでiOSアプリ開発に携わっているFelixです。今回は、8月22日(木)から24日(土)に開催されたiOSDCでの体験を共有したいと思います。前回のブログでお話した trySwift に続き、今回が二回目のiOSカンファレンス参加となります。今回はなんとスポンサーとして参加し、自社ブースを出展しました! KINTOブース 私たちのブースはKINTOブルーを基調としたデザインで、着用する法被もブランドカラーに合わせたものでした! ブースでは、参加者が実際に使われているプロジェクトコードを読み解き、対応する設問番号をコードにスタンプしていくコーディングチャレンジを開催しました。 またノベルティとして、KINTOのマスコットステッカーや、iOSデバイスを模した台紙を来場者に配布し、ステッカーで自由に台紙を飾りつけられるようにしました。また、コーディングチャレンジの参加者には、リワードとしてエコバッグまたはマルチチェーンのいずれかをお渡ししました。 このイベントは、参加者と交流し、KINTOテクノロジーズについての意見を伺い、プロジェクトに対する貴重なフィードバックを集める絶好の機会となりました。会話を通じて、当社の製品がどのように認識されているかについて新たな気づきを得ることができ、今後の改善に役立つ有益な指針を得られました。 他社ブース 他の企業のブースも非常に興味深く、教育的なものが多くありました。特に目立っていたブースをいくつか紹介します。 Sansan のブースでは、自社のテクノロジースタックが展示されており、来場者がさまざまなツールやフレームワークに触れてその反応を確かめられる内容だったため、興味深いものでした。 DeNA DeNAのブースでは、コードを読んだり地図を見たりしてクロスワードパズルを解くという楽しいアクティビティが用意されており、非常に印象的でした。 Bitkey Bitkeyのブースでは、ビーコンアプリを実装するためにテストデバイスとMacBookが必要でしたが、テストアプリを開発してビーコンを持っている人を見つけるという体験はとても楽しいものでした。 Glassfiber Glassfiberのブースでは、クイズを開催し、多くの来場者の興味を引き付けていて、楽しくてためになる内容でした。 プレゼンテーション いくつかのセッションに参加しましたが、特に印象的だった2つを紹介します。 StoreKit 2によるモダンなアプリ内課金 まず、StoreKit 2の最新の使用法についてのセッションです。これまでStoreKitを使った経験がなかったため、とても参考になる内容でした。このセッションでは、StoreKit 2の導入、実装、テストについて説明され、StoreKit 1との詳細な比較も行われました。プレゼンテーションでは以下の重要なポイントが取り上げられました:・async/awaitを使用した非同期処理の簡素化 ・レシート検証の効率化 ・サンドボックス環境、TestFlight、StoreKitTestを用いたテスト手法 アプリ内課金を統合しようとしている人にとって非常に有益な情報でした。特に興味深かった点として、StoreKitが「顧客が支払いをしたのに購入したアイテムを受け取れない」というケースを直接サポートしていないことが挙げられます。この点には驚かされました。 StoreKit 2によるモダンなアプリ内課金 このセッションでは、StoreKit 2の実装とテストについて説明され、アプリ内課金の改善点とその簡素化に焦点を当てていました GPSでどのようにして現在地が分かるのか もう一つ興味深かったセッションは、モバイル端末が信号を受信し、位置を計算する仕組みについてのものでした。セッションでは、Core LocationがGPS、Wi-Fi、携帯電話の信号をどのように組み合わせて正確な位置を特定するかについても説明されました。基本的なGPSの原理である衛星との三角測量が紹介されただけではなく、非常に遠い、弱い信号を受信する際の複雑なエンジニアリングについても詳しく触れられていました。また、スマートフォンがネットワークデータを活用して迅速かつ正確に位置情報を取得する方法についても解説されました。私はこの分野についてほとんど知らなかったため、とても啓発的で多くの学びが得られるセッションでした。 GPSでどのようにして現在地が分かるのか この動画では、GPSとネットワークデータを活用して、スマートフォンがどのようにして迅速かつ正確に位置を特定するのかについて詳しく説明されていました。 結論 全体として、iOSDC 2024は素晴らしい経験でした。洞察に満ちたセッションから多くを学ぶ機会であると同時に、より広範なiOS開発者コミュニティと交流する貴重な場でもありました。KINTOのブースを主催することで、多くの才能ある方々と直接交流し、彼らのフィードバックを聞きながら、自分たちの仕事を有意義な形で紹介できたことは、とても意義深いものでした。また、私が参加したプレゼンテーション、特にStoreKit 2とGPS技術に関するセッションは、KINTOで現在進行中のプロジェクトに直結する、実用的で有益な情報を得ることができました。StoreKit 2のasync/awaitの改善により、アプリ内課金の実装が大幅に合理化され、プロセスがより効率的でユーザーフレンドリーなものになると確信しています。同様に、GPSやネットワーク三角測量の高度な利用方法は、アプリ内の位置情報サービスをさらに強化し、ユーザーにとってより正確で迅速な結果を提供できるようになるでしょう。これらの学びを開発プロセスに統合し、開発者として成長を続けながら、プロジェクトの改善にも活かしていきたいと考えています。最後までお読みいただきありがとうございました!
アバター
はじめに こんにちは、9月入社のkhです! 本記事では2024年8、9月入社のみなさまに、入社直後の感想をお伺いし、まとめてみました。 KINTOテクノロジーズに興味のある方、そして、今回参加下さったメンバーへの振り返りとして有益なコンテンツになればいいなと思います! Naito 自己紹介 Engineering Officeに所属しています。プロダクト横断のプロセス改善を担当しています。 所属チームの体制は? Engineering Officeは1月にできたばかりで、2人です。 KTCへ入社したときの第一印象?ギャップはあった? 入社前の面談等で色々お話うかがっていたので特にギャップはなかったです。 拠点が東京・名古屋・大阪にあるのですが行き来が活発で、配属初日に名古屋所属の人たち複数にご挨拶して、名古屋って近いんだな〜とおもいました。 社内でいろんな方とお話するようになって感じたのは、「カイゼン」が日常で行われていること みなさんトヨタ生産方式(TPS)を知っていて、ちょっと感動しました。 現場の雰囲気はどんな感じ? いろんな開発チームに話をきいたり、相談しにいくのですがみなさん本当に親切ですし、巻き込まれてくれて日々感謝です! ブログを書くことになってどう思った? 入社前からテックブログは見ていました。書く側にまわれてうれしいです。 Mariさんからの質問:この会社を選んだ理由はなんでしょうか? 私自身、日常生活で車に親しんでいるので関心があるドメインであることと、今後ドンドン変化していきそうな会社だなとおもっていてその変化を体感し、楽しみたいとおもいました。 R.Y. 自己紹介 香港出身、2023年来日、学校を卒業して、今年8月KTCに入社し、現在セキュリティプライバシーGに所属しております。 所属チームの体制は? セキュリティプライバシーGに業務によってチームを分けられます。私はディフェンスチームに所属し、主に脆弱性診断とセキュリティ相談チケットを対応いたします。 KTCへ入社したときの第一印象?ギャップはあった? オフィスが広いです。フリースペースがたくさんあってみんな自由に使えます。勉強会と交流会が充実していて、業務範囲外の知識も身に付けられます。 現場の雰囲気はどんな感じ? みんな親しみがあって、話しかけやすいと思います。Slackで業務以外に、趣味や地域のチャンネルもたくさんあって、同じ趣味を持っている方と交流しやすい環境です。 ブログを書くことになってどう思った? 入社三ヶ月以来経験したことを見返して、入社予定のみんなに共有できて嬉しいです。 Naitoさんからの質問:ご出身地(香港)に旅行で行ってみたいです。オススメの観光地やお食事があったら教えて下さい! ディズニーのファンであれば、ぜひ香港ディズニーランドに行ってみてください。コンパクトなので1日で回りやすいパークです。 食事としては、B級グルメがたくさんあります。例えば、弾力のあるプリプリとした食感のカレーフィッシュボール、魚肉から作ったシュウマイ、紅茶の味が濃い香港スタイルミルクティーなどがおすすめです。香港では食べ歩きが普通ですので遠慮なくこういう文化を体験してください。 (ちなみに日本でも香港流行っている食事が食べられますので、香港に興味がある方ぜひご覧ください 譚仔三哥(タムジャイサムゴー)公式サイト ) tHyt 自己紹介 モビリティプロダクト開発部に所属するtHytです。一部エンジニアリードをやりつつ、主にフロントエンドエンジニアとして日々仕事をしております。 所属チームの体制は? チームとしては9名在籍しており、フロント・バック・インフラと各方面のエキスパートが集まっています。 KTCへ入社したときの第一印象?ギャップはあった? まだ若い会社ということもあり、制度として整っていない部分も多いのかと思いきやしっかり整備されていたのでびっくりした記憶があります。 現場の雰囲気はどんな感じ? Osaka Tech Labにて勤務していますが、メンバーがそこまで多くないためチーム問わず会話が発生していて飽きません。チームのメンバーは自分を除いて室町オフィスで勤務していますが、日々のミーティングや出張などを通して楽しく仕事できています。 ブログを書くことになってどう思った? 入社前から入社エントリーの存在は知っており、ついに自分が書く日が来たか…!という気持ちです。自己紹介のみならずテックブログでアウトプットをしていきたいなと思います。 R.Y.さんからの質問:入社後、慣れてなかったことがありますか?どうやって乗り越えましたか? 圧倒的に出張ですね…。自分のチームで大阪に勤務しているのは自分だけとなるため、今はコミュニケーション促進のために月1程度の頻度で東京へ出張しています。(出張を許可していただけるのは非常にありがたい) KTCに入るまで出張は1回しかしたことがなかったですし、新幹線に普段乗らないので最初は不安だらけでした。 今では慣れてきたのもあり、出張については特に不自由なくできるようになりました。 リモートでもコミュニケーションは取れるのですが、対面で行うコミュニケーションはまた一味違うので継続していきたいなと思っています。 kh ![alt text](/assets/blog/authors/kh/kh.png =250x) 自己紹介 分析Gに所属しています。分析するためのデータの収集、基盤を構築しています。 所属チームの体制は? リーダーと自分含めて10名になります(協力会社さん、派遣さん含む) KTCへ入社したときの第一印象?ギャップはあった? 新しめな会社なので、ベンチャー気質と思っていましたが、程よいゆるさを感じました。 現場の雰囲気はどんな感じ? 様々なバックボーンを持った集まりで、それぞれの異なる良さを活かし、和気藹々としています。 ブログを書くことになってどう思った? 元々TechBlogで積極的に発信していることを認識していたので、その第一歩を踏み出したのかなと思いました。 tHytさんからの質問:データ分析のコツみたいなのがあれば教えてください! 自分はデータ分析をすると言うより、データ分析をするための基盤を作ることがメインなので、ほしい回答とは異なるかもしれません。いかにすごいデータ分析を行なっても、使用するデータの品質が悪いと全く役に立つものにはなりません。分析基盤からデータの品質を確保することがデータ分析のコツの第一歩と思います。 woshahua 自己紹介 Woven payment solution開発Gに所属してる高です!中国出身ですが、関西弁が根付いてるエンジニアです。趣味はバスケ、サウナとポーカー、最近はプライベートでポーカーの勝率分析のアプリを作ってます! 所属チームの体制は? エンジニアメインの体制です、Woven側のエンジニアとも一緒に仕事させていただいてます。所属組織には関わらず目指す目標に対してチーム一丸で走れているのがとても良いです。 KTCへ入社したときの第一印象?ギャップはあった? 車領域ではまだ新しい会社ですが、幅広く色々なサービスや開発に挑戦してる印象でした。 ギャップはほぼなかったですが、入社後はより会社が社員の技術発信を応援してる点やほぼ毎週何らか社内勉強会が実施されてることに驚きました。 現場の雰囲気はどんな感じ? 技術経験に縛られず、広い技術範囲で色々挑戦できる環境だと思います。 困ったことがある際はすぐチームメンバーが助けてくれるので、とても恩恵を受けてます。 ブログを書くことになってどう思った? 会社としても、チームとしても積極的に技術発信を応援してると思うので、積極的に色々発信していきたいです! khさんからの質問:ペットを飼われていると伺いましたが、ペットとの生活で日常や仕事に影響を与えるものはありますでしょうか? 家でモルモットを飼っているんですが、仕事終わりにとても癒されています。在宅勤務もできるので、ペットと一緒に過ごす時間が増えて嬉しいです。仕事する上で心理的な余裕は良いパフォーマンスにも貢献できると思うので、自分に対してペットは仕事の緊張を緩めて心理的な余裕を与えてくれていると思ってます! Mari 自己紹介 開発支援部人事G採用Tに所属しています。エンジニアを中心に採用活動をしています。 所属チームの体制は? 私含め、6名です(1月からは全員室町オフィスにいます!) KTCへ入社したときの第一印象?ギャップはあった? オフィスが素敵!エンジニアの方々がみんなコミュニケーションとりやすく明るく楽しいと思いました! 現場の雰囲気はどんな感じ? みんな優しくて裏表がなく楽しい雰囲気です!真面目で、周りの人に対して、いつも気を配っている人しかいません。 ブログを書くことになってどう思った? 何を書こうか悩みましたが率直な自分の今の気持ちを書きました!みんなのブログも参考にしました! woshahuaさんからの質問:仕事中や休日に行うリフレッシュ方法はありますか? 仕事中の時はリフレッシュするために散歩に行ったりします。休日はなるべく外出したりジムに行ったりして体を動かすようにしています。 C4 ![alt text](/assets/blog/authors/kh/20250123/c4.jpg =250x) 自己紹介 KTC所属ですがKINTO管理部へ出向しており、主にオフィス総務を担当しています。 MBTI診断がISFJ-Tだったので管理部ってなかなか向いているのではないかと勝手に思っています。 所属チームの体制は? 所属チームで名古屋に勤務しているのは私を含めて8名です。 KTCへ入社したときの第一印象?ギャップはあった? 凄くお洒落なオフィスだなという印象でした。 オフィス勤務は初めてで、もっと堅苦しいイメージをしていたんですがチームの雰囲気は柔らかくフレンドリーですし勤務外でも食事に行ったりとコミュニケーションが盛んなのだなと感じます。 現場の雰囲気はどんな感じ? 自チームだけに限らず、まわりの皆さんがとにかく優しいと感じます。忙しそうな中で質問をしたりしても、嫌な顔をされた事がありません。 毎週1on1やチームMTGがあるので、チームメンバーがどんな動きをしているのか自分が関わっていなくても知る事が出来ますし自分の業務の困り事なども相談出来るタイミングを作っていただけている事がすごくありがたいです。 ブログを書くことになってどう思った? 正直な所、自分が受け持っている業務が事務・庶務系なのでエンジニアでない私の話をテックブログへ掲載して果たしてどなたかの参考になれるのだろうか…と考えたりしました(笑) Mariさんからの質問:私はMBTI診断はESFPでした!診断結果が当たりすぎていて怖いと思っていますw特に「細かいことが苦手」という部分が当たっていると思っています。。ISFJ-Tという診断は結構当たっていますか?特に当たっている部分はどんなところですか? 「変化に抵抗がある」 「完璧主義」 「自分に批判的」 の部分がグサッときました(笑) 人に喜んでもらえるなら自分のことは後!なマインドもかなりあるので結構当たっているなと私も感じました。 自己分析に適していると思うのでMBTI診断、皆さんにオススメです!! Mari 自己紹介 モビリティプロダクト開発部 DX企画推進G DX Planningチーム に所属のUI / UXデザイナーです。販売店様の業務DX化をサポートするプロダクトのデザインを行なっています。 所属チームの体制は? チームリーダー1名に、ディレクター3名、デザイナー4名の体制です。 KTCへ入社したときの第一印象?ギャップはあった? 母体が大きな会社なので仕組みやルールはしっかりしている一方で、新しい会社でもあるので雰囲気は自由です。ギャップは特になかったです。 現場の雰囲気はどんな感じ? タスクは多めで忙しいですが、その分やりがいがあり、活気にあふれた充実した雰囲気です。人間関係の雰囲気はとても良く、和気あいあいとしています。 ブログを書くことになってどう思った? 何を書こうか悩みましたが、素直な感想を書きました!他の方のブログも読むのが楽しみです。 C4さんからの質問:ご自宅にお猫がおられるとの事!我が家も猫×5と同居していますので今まで購入されたお猫様グッズの中で1番反応が良かったオススメを教えて下さい。(おやつでもおもちゃでもなんでもOKです!) 5匹 !! よいですね〜 🥰 うちの子は女王様気質で何を買っても「そんな子供だましのもの、興味ないんだけど」って態度であしらわれるんですが、やはりちゅーるは麻薬みたいで、特に高級食材が使われてるちょっと高いやつだと満足そうです😂 あと、本にゃんのご意向はわかりませんが、mirutoという自動猫トイレを購入して、掃除も楽だしかなり役に立っております !! ちなみに購入したキャットタワーは1度も乗る事なく1ヶ月以上経過し、泣く泣くそのまま粗大ゴミになりました….😇 さいごに みなさま、入社後の感想を教えてくださり、ありがとうございました! KINTO テクノロジーズでは日々、新たなメンバーが増えています! 今後もいろんな部署のいろんな方々の入社エントリが増えていきますので、楽しみにしていただけましたら幸いです。 そして、KINTO テクノロジーズでは、まだまださまざまな部署・職種で一緒に働ける仲間を募集しています! 詳しくは こちら からご確認ください!
アバター
目次 はじめに この記事の対象者 GitHubリポジトリ スケルトンバッチの解説 JobとStepの基礎 コード解説JOBクラス 実際にバッチを動かしてみる DBとCSVのバッチ解説 概要 ローカルDBのセットアップ マルチデータベース設定 Repositoryクラスとjooqの解説 DBからCSVバッチ解説 CSVからDBバッチ解説 継続的インテグレーション 終わりに はじめに こんにちは。KINTOテクノロジーズの共通サービス開発グループ[^1][^2][^3][^4][^5]のエンジニア、宮下です。 今回、Spring Boot 3を使ったバッチ処理の開発を担当しました。久しぶりのバッチ作成で、基礎を思い出しながら作業を進める中で、Spring Batchのクラス構成やアノテーションに苦労しました。 特に困ったのは、Spring Boot 3に対応した日本語情報が少なかったことです。公式ドキュメントを参考にしながら試行錯誤しましたが、自分の要件に合う設定を見つけられず、マルチデータベース構成やエラー対応でつまずくこともありました。その結果、動かしては修正するという悪循環に陥り、1からバッチを作り上げる大変さを痛感しました。 この記事では、その経験を活かして同じ悩みを抱える方がスムーズにSpring Batchを導入できるように、以下の内容をまとめました。 Spring Boot3 バッチのスケルトン GitHubからクローンして実行するだけで、すぐに動くバッチが手に入ります。あとは、業務ロジックを追加するだけで、バッチ開発が完了します。余計な設定や準備に悩む必要はありません。 バッチ処理によくあるユースケースの実装例と解説 スケルトンとは別に、以下のマルチデータベース構成を活用したバッチ処理のサンプルコードも紹介します。 1. DBのレコードをCSV出力するバッチ 2. CSVの内容をDBに登録するバッチ この記事を通じて、Spring Batchの導入や開発が少しでも簡単になれば幸いです。 この記事の対象者 以下のような方々を対象にしています: 初めてSpring Batch 5 のフレームワークでバッチを開発をする方。 Spring Batchに触れるのが久しぶりで、ジョブやステップなどの基本的な実装方法を思い出す必要がある方。 Spring Boot2 Batchのサポート終了で、3へのバージョンアップに伴う新しい構文や設定変更で困っている方。 本当はJavaのmainメソッドで動くシンプルなバッチが好きだが、Springバッチで作る必要が出てきた方。(私もそうです) とにかく手早くバッチの作成タスクを終わらせたい方。 これらに当てはまる方の参考になればと思います。 githubリポジトリ https://github.com/kinto-technologies/SpringBoot3BatchStarter/ こちらが、私のリポジトリです。クローンとお使いのIDEへの取り込みをお願いします。 GUIでクローンしたい方は、 GitHub Desktop の公式アプリを使うと、簡単にリポジトリをクローンできます。 リポジトリのディレクトリ構成 リポジトリは以下のように構成されています。 . ├── gradlew # Gradleラッパー ├── settings.gradle ├── compose.yaml # Docker Compose設定ファイル ├── init-scripts # データベース初期化用SQLスクリプト │ ├── 1-create-table.sql │ └── 2-insert-data.sql ├── dbAndCsvBatch # “DB to CSV” と “CSV to DB” のバッチプロジェクト │ ├── build.gradle │ └── src │ ├── main │ └── test └── skeletonBatch # スケルトンバッチプロジェクト ├── build.gradle └── src ├── main └── test スケルトンバッチの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/skeletonBatch 業務ロジックを追加するだけでバッチが即完成する、必要なコードのみで構成したスケルトンコードです。 JobとStepの基礎 スケルトンに業務ロジックを追加して動かすだけでも良いですが、フレームワークの基礎を理解しておくと、より安心して使えますよね。ここでは、Springバッチの核となる Job と Step について、手短に解説します。 Jobについて Jobとは、バッチ処理の全体を管理する単位で、1つまたは複数のStepを含みます。これにより、バッチ処理を効率的に実行・管理できます。Spring Batchでは、複数のJobを定義することができ、各Jobを個別に実行したり、連携させて実行することも可能です。 graph TD A[Job] --> B[Step 1] A --> C[Step 2] A --> D[Step 3] style A fill:#f9f,stroke:#333,stroke-width:4px Jobでできること 機能 説明 ユースケース 連携実行 あるジョブの成功後に次のジョブを実行 日次バッチの順次実行 失敗時の実行 ジョブ失敗時にリカバリー処理を実行 エラーログの出力、通知送信 並行実行 複数ジョブの同時実行 独立した処理の効率化 並列実行 同一ジョブの複数インスタンス実行 大量データの分散処理 Step(ステップ)の基礎 Stepとは? ジョブ内の各処理単位を表します。1つのジョブには複数のステップを登録でき、ステップの動作もジョブと同様に柔軟に設計可能です。 実装方式の選択 - ここがポイント! Stepの実装には Chunk と Tasklet の2つの方式があります。 1.チャンク(Chunk)処理 大量データを効率的に処理するための方式です。 graph LR A[Reader] -->|100件ずつ| B[Processor] B -->|1件ずつ| C[Writer] C -->|100件まとめて| D[DB/File] 特徴 3つのフェーズで処理を分割 Reader: データを一定量ずつ読み込み 例: FlatFileItemReaderで、CSVファイルを100行単位で読取 Processor: データを1件ずつ加工 例: CustomProcessorで、日付フォーマットの変換 Writer: まとめて出力 例: JdbcBatchItemWriterで、DBへの一括INSERT トランザクションの単位は、チャンクサイズ(例:100件)ごとにコミットされます。つまり、途中でエラーになっても、正常終了したチャンクサイズ分はコミットされます。 2.タスクレット(Tasklet) 処理 シンプルな処理を1つ実行する方式です。 特徴 シンプルな実装 分かりやすい処理フロー デバッグが容易 実装方式の使い分け 観点 Chunk Tasklet データ量 大量に適する 少量に適する 処理の複雑さ 複雑な処理に適する シンプルな処理に適する 管理コスト 高め 低め デバッグ やや複雑 容易 トランザクション チャンク単位 処理単位 まとめ Jobはバッチ処理全体の管理単位 Stepは具体的な処理の実行単位 Chunkは大量データの効率的な処理に適する Taskletはシンプルな処理に最適 処理内容に応じてChunkとTaskletを適切に使い分けることが重要 :::message ポイント 複雑な処理が不要な単純なバッチでは、迷わずTaskletを選択しましょう。 今回提供するスケルトンバッチでも、もちろんTaskletを採用しています。 ::: :::message alert 注意 公式ドキュメントを初め、多くのWeb記事ではチャンク処理がスプリングバッチの基本として詳しく説明されているため、初心者の方は「チャンクで作らないといけない」と誤解しがちです。そのため、チャンクで実装を進めた後に「やっぱりタスクレットで作ればよかった」となり、手戻りが発生するケースが多いです。 ::: YAML設定とSpringバッチの管理テーブル まず、Spring Batchをバッチ専用モードで動かす基本的な設定について説明します。 YAML設定 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/resources/application.yml この設定により、Spring Bootアプリケーションはサーバーレスモードで動作します。 通常のSpring BootアプリケーションではTomcatなどのサーバーを起動してプロセスを維持しますが、バッチ処理ではそれが不要です。この設定により、バッチ処理が完了するとプロセスが終了します。 Spring Batch 管理テーブル Springバッチでは、実行結果を記録するための管理テーブルをDB上に作成する必要があります。でも、DBやテーブルの管理ってちょっと面倒ですよね。そもそもDBが無い環境で動かすこともあるはずです。その場合は、H2のオンメモリDBを使うのがおすすめです。プロセスが終わるとメモリが開放されてDBとテーブルが無くなります application.ymlにDB設定を書かなければ、この設定が自動で適用されます。 ただし、永続化のメリットとしては、実行結果を保持し、途中で中断した場合でも再開可能な状態を維持できる点が挙げられます。 コード解説JOBクラス https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/job/SampleJob.java#L16-L41 Spring BatchでJobを登録・実行するための中心的なクラスです。 クラス定義のポイント @Configuration Spring Batchの設定クラスとして認識されます このアノテーションにより、このクラスがSpringの設定を行うクラスだと認識されます Jobを定義するクラスには必須のアノテーションです @Bean Spring Frameworkで管理したいオブジェクトを生成するメソッドに付けます この場合、 createSampleJob メソッドが生成するJobをSpringが管理できるようになります バッチ処理実行時に、このメソッドで作られたJobが使用されます :::message alert 注意 メソッド名がデフォルトでBean名として使用されます。このクラスをコピーして新しいJobを作る場合、メソッド名が同じだとエラーになります。メソッド名が重複する場合は、@Bean("jobName") のように一意な名前を指定しましょう。意外なハマりポイントなので注意して下さい。 ::: 依存クラスの役割 JobRepository : ジョブの実行状態を管理します PlatformTransactionManager : データベースの整合性を保つために使用します SampleLogic : 実際の業務処理を行います 処理の流れ ジョブ登録開始のログを出力 Stepの作成(処理単位の定義) Jobの作成とStepの登録 ジョブ登録完了のログを出力 トランザクション管理 正常終了: すべての処理が成功すると、データベースの変更が確定されます 異常終了: エラーが発生すると、データベースの変更が取り消されます これにより、バッチ処理の信頼性が保証されます。 ポイント @ConfigurationアノテーションでSpring Batchの設定クラスとして認識されます JobBuilderFactoryとStepBuilderFactoryを使用してジョブとステップを構築します Taskletパターンを採用することで、シンプルな実装を実現しています Logicクラスの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/logic/SampleLogic.java#L13-L35 このクラスは、バッチの実際の処理内容を定義するものです。 クラス定義のポイント @Component このクラスをSpringで管理できるようにします これにより、他のクラスで@Autowiredして使用できます Taskletインターフェース Spring Batchの処理単位を表すインターフェース executeメソッドに実際の処理を実装します 処理の流れ バッチ処理開始のログを出力 SampleServiceのprocessメソッドを呼び出し 正常終了時は完了ログを出力 エラー発生時は例外をログ出力して再スロー バッチ処理終了のログを出力 エラー処理 正常終了: RepeatStatus.FINISHED を返してバッチを完了 異常終了: 例外をキャッチしてログを出力し、上位に例外を通知 :::message 実装のポイント このクラスでは処理をSampleServiceに委譲していますが、シンプルな処理の場合はexecuteメソッド内に直接実装しても問題ありません。 ::: Serviceクラスの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/service/SampleService.java#L8-L23 このクラスは、実際の業務ロジックを記述するためのクラスです。 クラス定義のポイント @Service Spring Frameworkのサービスクラスとして認識されます ビジネスロジックを実装するクラスであることを示します processメソッド バッチで実行したい業務処理を実装するメソッド 処理開始と完了のログ出力を含みます :::message 実装のポイント このクラスの特徴は、Spring Batchのクラスやインターフェースから完全に独立しています。そのため 純粋な業務ロジックの実装に集中できる JUnitテストが書きやすい 他のプロジェクトでも再利用が容易 ::: カスタマイズ方法 以下のような変更が可能です: クラス名の変更 メソッド名の変更 引数や戻り値の追加 業務ロジックの実装(例:データ検証、変換処理、外部システム連携) これで、スケルトンバッチの主要なクラスの解説は終わりです。このように疎結合な設計にすることで、メンテナンスしやすく、テストも容易なバッチを実現できます。 実際にバッチを動かしてみる IDEから起動する場合 BatchApp クラスを起動してください。通常の Spring Boot アプリケーションと同じ要領で動作します。 ターミナルからGradle経由で起動する場合 以下のコマンドを実行してください。 ./gradlew :skeletonBatch:bootRun ターミナルから実行可能JARファイルを生成して実行する場合 Gradle のデフォルトタスクを実行すると、JAR ファイルが生成されるように設定済みです。以下の手順で実行可能です。 cd skeletonBatch ../gradlew java -jar build/libs/batch-skeleton*.jar ログから実際の動きを確認 Spring Batchの実行フローを、出力されるログから順番に確認していきましょう。 1. JOB登録の確認 ----------- Registering job: sample ----------- ----------- Job registered successfully: sample-job ----------- 解説 Spring Boot起動時に、sample-jobというバッチジョブが正常に登録されました。 2. バッチ処理の実行 Started BatchApp in 0.456 seconds (process running for 0.616) Running default command line with: [] Job: [SimpleJob: [name=sample-job]] launched with the following parameters: [{}] Executing step: [processing-step] ----------- START ----------- Batch Processing ----------- --- Starting batch process --- --- Batch process completed --- Processing completed successfully ----------- END ----------- Batch Processing ----------- Step: [processing-step] executed in 3ms Job: [SimpleJob: [name=sample-job]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 9ms 解説 ログから確認できる重要なポイント: sample-jobが実行され、processing-stepというステップが開始 開始(START)と終了(END)のログにより、処理の境界が明確に解ります。 ステップが正常に完了し、ジョブ全体がCOMPLETEDステータスで終了 アプリケーションが正常に終了 スケルトンバッチの解説まとめ 業務ロジックを追加するだけでバッチが完成 生成されたJARファイルはcronなどのスケジューラと連携可能 シンプルな構成で保守性が高い :::message Spring Batchの開発では、アノテーションの付け忘れや設定ミスが起きやすく、エラーメッセージの解読に時間を取られがちです。このスケルトンを使えば、そうした問題を避けて業務ロジックの実装に集中できます。 ::: このスケルトンコードが、皆様のバッチ開発の効率化に貢献できれば幸いです。 DBとCSVのバッチ解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/dbAndCsvBatch こちらのディレクトリです。 概要 このプロジェクトには2つのバッチ処理が含まれています: DBからCSVを出力 データベースからレコードを抽出してCSV形式で出力 起動時引数でデータの抽出条件を変更可能 デフォルト設定でもすぐに動作可能 CSVからDBへ登録 CSV形式のデータを読み取ってデータベースに一括登録 実行するにはCSVが必要なため、まずは上記の出力バッチを実行してください ローカルDBのセットアップ このバッチでは、MySQLのローカルDBを使用します。以下の手順でセットアップしてください。 MySQLコンテナの起動 docker compose up -d :::message Dockerが未インストールの場合は、 Dockerの公式サイト からインストールしてください。 ::: 動作確認 MySQLコンテナに接続してサンプルデータを確認します: docker exec -it mysql-container mysql -u sampleuser -psamplepassword sampledb サンプルクエリの実行: mysql> SELECT * FROM member WHERE delete_flag = 0 AND type IN (1, 2, 3) ORDER BY type ASC; mysql> exit Bye Entityクラスの自動生成 GitHubリポジトリをクローンした直後、Entityクラスが見つからないエラーが発生する場合があります。これはjOOQによるEntityクラスの自動生成が必要なためです。 以下のどちらのコマンドでもEntityクラスが生成されます。 自動生成の実行 # デフォルトタスクの実行 cd dbAndCsvBatch/ ../gradlew # または、generatejOOQタスクを直接実行 ../gradlew generateJooq build.gradleは追加の設定を行わなくても利用できるように工夫しています。デフォルトタスクを実行すると、以下の処理が動くように設定済みです。 ビルド結果のクリーンアップ Javaコードの整形(Google Java Format) Entityクラスの自動生成(jOOQプラグイン) コンパイル 静的解析とテスト(JUnit、カバレッジ、SpotBugs) 実行可能JARの生成 生成されたEntityクラス 以下のコマンドで生成されたEntityクラスを確認できます。 tree dbAndCsvBatch/build/generated-src #treeコマンドをインストールしていない場合はこちら ls -R dbAndCsvBatch/build/generated-src dbAndCsvBatch/build/generated-src/jooq └── main └── com └── example └── batch └── jooq ├── DefaultCatalog.java ├── Keys.java ├── Sampledb.java ├── Tables.java └── tables ├── Member.java └── records └── MemberRecord.java :::message alert jOOQプラグインの自動生成コードは、ビルド時にデータベーススキーマから再生成されるため、Gitで管理すると、スキーマの変更時に差分やコンフリクトが発生しやすくなります。そのため、生成されたコードはGitで管理しないことが推奨されます。 ::: クラスが見つからないエラー発生時は お使いのIDEによっては、Entityクラスが見つからないエラーが発生するかもしれません。 その場合は以下の方法でコンパイルエラーを解消して下さい。 IDEのビルドパスにgenerated-src/jooqを追加 generated-src/jooq配下のクラスをdbAndCsvBatch/src/main/javaにコピー これらの対応でコンパイルエラーが解消され、プロジェクトが正常にビルドされるようになります。 業務環境でのエンティティ自動生成方法 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/build.gradle#L101-L124 :::message 皆さんの開発環境のDEV, STGのDB接続設定に変更してEntityクラスを生成 または、Docker Composeの初期化SQLに本番のCREATE TABLE文を追加し、コンテナを再作成する。 ::: jOOQ gradle plugin website バッチの解説 長くなってきているので、スケルトンで解説した内容と、Springバッチフレームワークに直接関係ないCSV操作のコード解説は省略し、新しいバッチの設定についてのみ解説したいと思います。 マルチデータベース設定 このバッチでは、2つのデータベースを使用するマルチデータベース構成を採用しています。 1. H2 (オンメモリデータベース) Spring Batchの管理用テーブルとして利用。オンメモリDBなのでプロセス終了時にリセットされます。 MySQL (業務ロジック用データベース) 業務ロジックで使用。Dockerコンテナ上で動作します。 設定ファイル (application-local.yml) Spring Bootの設定ファイルでは、spring.datasource 配下に2つのデータベースを設定します。 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/application-local.yml 設定ファイル(yml)には2つのDBが設定されています。また、ファイル名の末尾に local と server のプロファイルを付けています。実行時引数で使用する設定ファイルを切り替えられるようになっています。server 用は、Dockerではなく、皆さんが業務で使用する MySQLサーバーの接続文字列に書き換える想定です。 データソース設定クラスの解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/config/DataSourceConfig.java#L11-L37 2つのデータベースをSpring Batchで使用するための設定クラスです。 アノテーション解説 @Configuration Springの設定クラスであることを示す このクラスで定義したBeanはSpringが管理する @ConfigurationProperties application.ymlの設定値を取得する 例: spring.datasource.mysqlmain の設定を読み込み @BatchDataSource Spring Batchの管理テーブル用のデータソースを指定 H2データベースに対して使用 @Primary 優先して使用するデータソースを指定 MySQLデータソースをデフォルトとして使用 Jobクラス解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/job/DbToCsvJob.java#L31-L39 新機能の追加 実行番号の自動採番 .incrementer(new RunIdIncrementer()) ジョブの実行番号(run.id)を自動的にインクリメント 実行履歴の管理に使用 :::message このサンプルコードでは、管理テーブルをオンメモリDB(H2)に設定しているので、 意味はありませんが、永続的なDBを利用する場合の標準的な記述方法として紹介しています。 ::: リスナーの追加 .listener(listener) ジョブの開始前と終了後に処理を追加 ログ出力やメール通知などに利用可能 Logicクラス解説 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/logic/DbToCsvLogic.java#L18-L49 パラメータ設定 @Value("${batch.types:2,3,4}") private String typesConfig; application.ymlに設定がない場合は、2,3,4がデフォルト値として使用されます 実行時に--batch.types=1,2のように引数を渡すと、その値が優先されます エラーハンドリング contribution.setExitStatus(ExitStatus.FAILED); エラー発生時に明示的に失敗ステータスを設定 正常終了時は自動的にCOMPLETEDとなる :::message 同じくH2なので意味はありませんが、標準的な記述方法として紹介しています。 ::: RepositoryクラスとjOOQの解説 アノテーション解説 @Repository Springのコンテナによって自動的に管理されます。 SQLException などのデータベース固有の例外を Spring の DataAccessException に変換します。 このクラスがデータアクセスに関わるものであることを明示的に示します。これにより、コードの可読性と保守性が向上します。 jOOQとは jOOQ は、SQLをJavaコードとして記述できるライブラリです。 SQLの構文をそのままJavaコードで表現 テーブル定義からEntityクラスを自動生成 タイプセーフなSQL操作を実現 Select処理 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L18-L25 主な特徴 SQLライクな記述 SQL文をJavaコードで直接表現 SQLの知識がそのまま活用可能 自動生成クラス解説 MEMBER : テーブル定義を表現(カラム名など) MemberRecord : レコードのマッピング用 バルク(一括)Insert処理 https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L54-L69 バルクInsert 1レコードずつinsertするのではなく、複数のデータを一括で登録します。 バッチ処理では、DB負荷軽減のため複数レコードをまとめて処理するのが一般的です。 :::message このバッチでは使いませんが、insert, update, deleteメソッドのコード例も載せています。 エンティティを職場環境に合わせて作成した場合は、テーブル名とカラム名を置き換えるだけで、すぐにリポジトリクラスとして活用することができます。 ::: Repositoryクラスのまとめ jOOQのGradleプラグインを使用することで、データベーススキーマからエンティティを自動生成して使用します。 自動生成されたクラスを使用することで、型安全性を確保しつつ、SQL構文を直感的にJavaで記述できます。 テーブル定義の変更があった場合でも、クラスを再生成するだけで対応可能です。 :::message Spring Batchでは、複数のデータベース操作方法が利用可能です。jOOQの他に代表的な選択肢として以下があります。 Hibernate (JPA) 強力なデータベース抽象化 Spring Bootとの統合が簡単 大規模データセットでオーバーヘッドが発生する可能性がある MyBatis 直接的なSQL管理に最適 動的クエリのサポートが強力 保守性が低下する可能性がある Spring JdbcTemplate 軽量なデータベース操作 シンプルなクエリに適している 記述が冗長になることがある ::: :::message alert 注意点 プロジェクトの要件やチームのスキルセットに応じて適切な方法を選択してください。必要であれば、ORMを使わずに直接SQLを実行する方法も検討可能です。 ::: DBからCSVバッチ解説 DBからCSVバッチを起動します。起動方法はスケルトンバッチと同じ要領です。 ターミナルからGradle経由で起動する場合 ./gradlew :dbAndCsvBatch:bootRun ターミナルから実行可能JARファイルを生成して実行する場合 cd dbAndCsvBatch/ ../gradlew java -jar build/libs/batch-dbAndCsv*.jar ここでは、スケルトンバッチの起動方法と同じく、起動引数を付けずにAppクラスを実行しました。 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobLauncherApplicationRunner' defined in class path resource [org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.class]: Job name must be specified in case of multiple jobs at com.example.batch.DbAndCsvBatchApp.main(DbAndCsvBatchApp.java:16) Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs エラーが発生しました。 なぜエラーが発生したのか? エラーの原因は、複数のジョブが定義されているため、フレームワークがどのジョブを実行するか判断できなかったことです。そのため、実行するジョブを明示的に指定する必要があります。このプロジェクトでは2つのジョブが定義されています。一方、スケルトンバッチにはジョブが1つしか定義されていないため、このエラーは発生しません。 起動引数を指定して実行する エラーを解消するには、実行時にジョブ名と環境名を起動引数として指定します。 IDEで実行する場合は、実行時引数設定に次の内容を指定してください。 --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local --spring.batch.job.name=DB_TO_CSV: 実行するジョブ名を指定します。 --spring.profiles.active=local: ローカル環境用の設定プロファイルを有効にします。 実行コマンドの例 # Gradleを使用する場合 ./gradlew :dbAndCsvBatch:bootRun --args="--spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" # 実行可能JARを直接使用する場合 cd dbAndCsvBatch/ java -jar build/libs/batch-dbAndCsv*.jar --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local 無事起動したと思うので、ログの内容を確認します。 ログ出力例 ##### KEY:"sun.java.command", VALUE:"com.example.batch.DbAndCsvBatchApp --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" ##### Spring Batch ##### - Job: DB_TO_CSV, Profile: local 起動引数をログ出力するコードを App クラスに仕込んでいます。 ログから、ジョブ名とプロファイル名が正しく渡されていることが確認できました。 ログ出力例 ----------- JOB [Job Name:DB_TO_CSV] START! ----------- Executing step: [DB_TO_CSV-step] Fetching members with types = [1, 2, 3] リスナー (BatchNotificationListener) の beforeJob メソッドのログが出力されています。 また、typesに設定ファイルの値が設定されたことが確認できました。 ログ出力例 -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (1, 2, 3)) order by `sampledb`.`member`.`type` Version : Database version is supported by dialect MYSQL: 9.1.0 Fetched result : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | id|type|name |email |phone |address |delete_flag|created_at |updated_at | : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | 1| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 2| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 26| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 27| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 3| 2|ABC Corp |contact@abccorp.com |5678901234|789 Pine St, Village, Country| 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ Fetched row(s) : 5 (or more) Batch process completed successfully. Step: [DB_TO_CSV-step] executed in 193ms Job: [SimpleJob: [name=DB_TO_CSV]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 212ms ----------- JOB [Job Name:DB_TO_CSV] FINISHED! status:[COMPLETED] ----------- https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/logback.xml#L21-L22 logback.xmlの設定で、jOOQのクエリとその結果をログ出力するように設定しています。実際のクエリーと結果が見られるとデバックしやすいですよね。 また、リスナーの終了ログも出力されており、バッチが正常に終了したことが確認できました。 生成されたCSVファイルの確認 "id","type","name","email","phone","address","deleteFlag","createdAt","updatedAt" "1","1","John Doe","john.doe@example.com","1234567890","123 Main St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "2","1","Jane Smith","jane.smith@example.com","0987654321","456 Oak St, Town, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "3","2","ABC Corp","contact@abccorp.com","5678901234","789 Pine St, Village, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "5","3","Alice Premium","alice.premium@example.com","4561237890","987 Maple St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "6","3","Charlie Davis","charlie.davis@example.com","1112223333",,"0","2024-12-11T06:05:26","2024-12-11T06:05:26" CSVファイルが出力された事が確認できました。CSVへの出力と、読み込みにはopencsvのライブラリを使用しています。 opencsv website 実行時引数での types の上書き確認 最後に、このバッチには実行時引数にwhereのin句に設定するtypesを起動引数で渡せる機能がありました。試してみましょう。以下の起動引数を指定することで、where の in 条件に指定する types をカスタマイズできます。 --spring.batch.job.name=DB_TO_CSV --batch.types=4,5 --spring.profiles.active=local ログ出力例 -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (4, 5)) order by `sampledb`.`member`.`type` 設定ファイルの値が、実行時引数で上書きされていることが解ります。 CSVからDBバッチ解説 バッチを起動します。実行時引数でジョブ名を指定して実行します。 --spring.batch.job.name=CSV_TO_DB --spring.profiles.active=local ログを確認します。 insert into `sampledb`.`member` (`name`, `email`, `phone`, `address`, `type`) values ('Premium Corp', 'premium@corporate.com', '8889997777', '555 High St, City, Country', 4), ('Elite Ltd', 'elite@elitecorp.com', '4445556666', '777 Sky Ave, Town, Country', 4), ('Guest User1', 'guest1@example.com', '', 'Guest Address 1, City, Country', 5), ('Guest User2', 'guest2@example.com', '9998887777', '', 5) Affected row(s) : 4 バルク (一括) でinsertされているのが確認できました。 継続的インテグレーション このプロジェクトでは、GitHub Actionsを使用してCI(継続的インテグレーション)を実現しています。コードの変更がプッシュまたはプルリクエストされるたびに、全ての処理が自動で行われ、品質管理が効率的に進められます。 主な流れ 1. MySQLのセットアップ: Docker Composeを使ってMySQLを起動し、必要なテーブルを確認。 2. JDK 21のセットアップ: Java 21をインストールしてビルド環境を整備。 3. jOOQでのクラス生成: データベーススキーマから自動でエンティティクラスを生成。 4. ビルドとテスト: Gradleでビルドを行い、JUnitやJacocoやSpotBugsを用いて品質チェックを実施。 コードの変更時、コンパイルエラーやJUnitテストエラーを即座に検出し、迅速に対応できます。 さらに、ビルドとテストが成功すると、GitHubリポジトリのREADMEに以下のようなバッジが表示され、プロジェクトの状態を一目で確認できます: コードカバレッジの可視化 このプロジェクトでは Codecov を使用してテストカバレッジを測定・可視化しています。プルリクエスト時にカバレッジレポートが自動生成され、以下のバッジでカバレッジ率を確認できます: これにより: テストの網羅性を視覚的に把握 カバレッジの変更を迅速に検知 品質管理の透明性を向上 カバレッジレポートの詳細はCodecovのダッシュボードで確認できます。 graph LR D[👩‍💻 Developer] -->|Push/PR| G[🐙 GitHub] G -->|Trigger| GHA[⚡ GitHub Actions] GHA -->|Build and Test Results| R[📄 README.md] GHA -->|Coverage Report| C[☁️ Codecov] C -->|Coverage Badge| R style G fill:#f4f4f4,stroke:#171515 style C fill:#f01f7a style R fill:#e6e6e6 終わりに いかがでしたでしょうか? このプロジェクトでは、スケルトンコードを基盤に、Spring Batchを活用したバッチ開発を効率よく進められるよう工夫しています。「DB to CSV」「CSV to DB」といったよくあるユースケースを取り上げ、DB設定やテーブル定義、CSVレイアウトを変更するだけで簡単にカスタマイズできる柔軟性を備えています。 このスケルトンを活用して、皆さんの業務に合わせたロジックを追加し、バッチ開発がスムーズになることを願っています。 この記事が参考になった、コピペでバッチが動いた!という方は、GitHubリポジトリに⭐を付けていただけると嬉しいです。 最後までお読みいただき、ありがとうございました🙇‍♂️ [^1]: 共通サービス開発グループメンバーによる投稿 1 [ グローバル展開も視野に入れた決済プラットフォームにドメイン駆動設計(DDD)を取り入れた ] [^2]: 共通サービス開発グループメンバーによる投稿 2 [ 入社 1 年未満メンバーだけのチームによる新システム開発をリモートモブプログラミングで成功させた話 ] [^3]: 共通サービス開発グループメンバーによる投稿 3 [ JIRA と GitHub Actions を活用した複数環境へのデプロイトレーサビリティ向上の取り組み ] [^4]: 共通サービス開発グループメンバーによる投稿 4 [ VSCode Dev Container を使った開発環境構築 ] [^5]: 共通サービス開発グループメンバーによる投稿 5 [ MinIOを用いたS3ローカル開発環境の構築ガイド(AWS SDK for Java 2.x) ]
アバター
Happy Christmas🎄 Hello! I am Watanabe from the KINTO ONE Development Group, where I work as a front-end engineer specializing in product development with Next.js and TypeScript. I also contributed to the development and launch of our Tech Blog as part of the Tech Blog Management team. On the final day of our Advent Calendar, I’ll be sharing insights into the design choices and front-end development process behind the Tech Blog. Assembling the Tech Blog Team The Tech Blog management team was originally formed by four volunteers, led by Nakanishi-san, with the aim of fostering a culture of output within the company. We discussed the operation system and blog specifications with the awareness of visualizing the remaining tasks, and I recall how quickly decisions were made. Among the many roles on the Tech Blog, I was responsible for the overall design and front-end implementation. During weekly meetings, ideas from our discussions were incorporated into prototypes and shared with the team. Prototypes motivates the team, so as the implementation manager, I approached development while focusing on visible progress. Building the Tech Blog with Next.js The front-end framework (library) we use is Next.js. I chose Next.js because I'm most familiar with it. From the concept stage, I envisioned storing blog posts as Markdown files and generating a static site using a Static Site Generator, which seemed like a great fit. The static content is generated at build time from the project, and the deliverables are then hosted. Next.js also publishes examples on GitHub that combine various libraries. One of these was blog-starter , which provided a template for the blog, so it was easy to get started, which is another reason I chose Next.js. Based on the above, I categorized the steps needed to create our Tech Blog into the following three development stages: Design Selection Screen Implementation (article list page, article detail pages) Preparing Markdown Tools Design Selection I took on the responsibility for the design, but since I had no prior experience with blog design, I began by researching case studies from other companies. Taking the features and findings obtained from other companies' case studies into account, our team organized the UI elements and information necessary for the Tech Blog. Documentation of features and findings During our design research, we focused on four components—header, cards, list, and footer—with the goal of creating a simple and easy-to-understand design. Since I had decided to take responsibility for the implementation as well, I created the design with easy coding and scalability in mind. Component Classification When the design was nearly finished, I consulted with the site design department and received their feedback. Screen Implementation Since the screen template was provided by blog-starter, we adopted Tailwind CSS as our CSS framework to focus on coding styles. Tailwind CSS uses well-defined, standard class names, making it easy to recognize the styles they represent. Its utility-first approach makes style adjustments intuitive, ultimately lowering development costs. Several of our in-house front-end engineers have contributed to enhancing the tech blog and fixing bugs. I believe this framework is well-suited for team development because they can implement it smoothly with minimal learning costs. The ability to write responsive support within the same className is also a great advantage. <div className="md:px-6 lg:px-10 md:py-10 md:hover:shadow-xl"> <div className="mb-6"> <CoverImage slug={slug} title={title} src={coverImage} /> </div> <div className="flex items-center mb-4 md:mb-6"> <div className="background-color h-7 flex items-center rounded-lg p-2 md:p-4 mr-3"> <span className="text-white text-xs md:text-base">{category}</span> </div> <div className="text-gray-500 text-sm md:text-base transition"> <DateFormatter dateString={date} /> </div> </div> <h3 className="text-xl sm:text-2xl md:text-2xl lg:text-3xl mb-2 md:mb-6 leading-snug text-color"> <Link href={`/posts/${slug}`}> <a className="hover:underline">{title}</a> </Link> </h3> </div> Preparing Markdown Tools Our Tech Blog uses zenn-markdown-html and zenn-markdown-css as Markdown parsers. We chose these because the default Markdown notation provided by blog-starter lacks variation and is difficult to customize. The role of each parser is as follows. zenn-markdown-html Package for converting Markdown to HTML (markdownToHtml), including Zenn's own notation zenn-content-css CSS for applying to HTML converted from Markdown with zenn-markdown-html Specify className=znc to the component or block to which you want to apply CSS A wide range of embedded content is also available, making it easy to create engaging blogs. (Content can be easily displayed by simply stating the URL of the Tweet.) https://x.com/KintoTech_Dev/status/1597900747538046978 Functions I Want to Try in the Future The Tech Blog started small with the goal of being simple and easy to understand. I'll also summarize ideas for functions that I want to implement in the future. Since its release, we've received many comments from both inside and outside the company, asking, "Why not add this function?" I'm thrilled to see that we're getting more attention every day. We are actually working on updates, such as adding RSS function . Implementation of article search and refinement functions by adding tags and categories Implementation of localization function to support multiple languages Move content management to Micro CMS or cloud servers, and stop local management Summary Despite the limited resources, we were able to explore new front-end technologies and had a lot of fun developing. Furthermore, the growing number of people submitting pull requests and reporting issues suggests that a blogging culture is gradually taking root within the company. While there is still room for improvement in the Tech Blog, I'll continue to do my best and stay updated with the latest technologies. Conclusion I hope this year's advent calendar helps you learn more about our company's initiatives and how our employees work! Furthermore, KINTO Technologies is looking for people to work with us! You can find more information here Thank you for reading my article all the way to the end! Reference List of Zenn's Markdown Notation
アバター
KINTOテクノロジーズの景山です! 年末恒例ですが、2024年の振り返りと2025年の展望について書こうと思います。 2024年の振り返り 振り返ると、1年前とくらべて、やるべきことが増えました。 それにともなって、社員も増え、組織も拡大しました。 一方で、組織が拡大しても、内製開発組織としてのメリットを失わないように手をうってきたつもりです。 今年は本格的に販売店のデジタルトランスフォーメーションのサポート(販売店DXプロジェクト)が立ち上がりました。 すでに見積もり関連で販売店の工数を削減するツールや、お客様管理アプリの提供が始まっています。 また、AIを活用したプロダクト開発も順調に進んでいます。 AIやクラウド利用が進むにあたり、セキュリティもガバナンスする範囲が広がっています。 クラウドセキュリティの専門部隊の設置や、AIプロジェクトのセキュリティについても取り組んできました。 社内のAI活用への取り組みも強化してきました。 社内コミュニケーションツールのSlackとAIを組み合わせた仕組みは社内で一般化してきています。 非エンジニア社員からは生成AIを活用して、アプリケーション開発をしてしまうような例も出てきました。 さらにグループ会社や販売店への生成AI研修もはじめました。 重要施策の進捗 重要施策として、 昨年のアドベントカレンダー では下記3つを高めていく、と書きました。 技術力 開発生産性 リリーススピード まず、技術力については、トヨタ自動車およびグループ会社へのテック支援が増えました。 ITベンダー各社からも評価されるユースケースを作ることもできました。 とくに、テックブログは、多くの読者に支持され、なんと年間12万ユニークユーザーを達成しました。 多くの人が執筆に参加してくれるようになり、その結果、社外からの反響も大きくなっています。 つぎに、開発生産性については、Findy Team+で定量的に自分たちの生産性を把握し、個々の工程の生産性向上に資する取り組みが広がっています。また生成AIを活かした仕組みの導入、例えば、GitHub Copilotの活用など社員の開発生産性向上の意識も高まり、ビジネス企画からリリースまでの全体スループットをあげる取り組みなどいくつもの新しいイニシアティブがスタートしています。 また、リリーススピードについては、おもに要件定義までの工程の圧縮をはかってきました。要件定義工程を開発側がリードすることで実現可能かつ開発工数が少ない方法でプロダクト開発を行う方向にビジネス側をリードする、ということができはじめています。 まだまだ試行錯誤の段階でもありますが、このスピードに関する意識を社内でもっともっと浸透させたいと思います。 2025年の展望 では来年はどうするかというと、下記2つの「ファースト」に注力したいと考えています。 AIファースト リリースファースト(最短リリース) AIファースト AIファースト は文字通りなのですが、次のような動きを強めていきます。 すべてのプロダクトにAIをインテグレートしていく AIプロダクトを多く開発する 販売店、トヨタグループのAI活用を推進するドライバーになる これまでの社員みなさんの取り組みのおかげで、今年はこれらを推し進める下地が十分にできました。 来年は多くのアウトプットを出していけるはずと期待しています。 これからはAIを使わなくてもできるものでも、AIをあえて使って開発してみよう、というマインドが重要だと思います。そうした中で我々がAIファーストを実現するためのアイディアやコラボレーションが生まれてくることを期待しています。 リリースファースト リリースファースト(最短リリース) は、われわれが開発するプロダクトをいかに最短でリリースするか、知恵と技術を使ってここにこだわっていきたいと思っています。 ビジネスオーナーやプロダクトごとに最短リリースするためにやるべきことは異なります。 今年は一部のプロジェクトやチームでの要件定義工程及び、実装工程の生産性向上がメインでした。 来年は特定の工程の生産性向上にとどまることなく、各プロダクトチームが自分たちのプロダクトをステークホルダーも巻き込みどこをどうしたらリリース期間を短くし、最短リリースを実現できるのかを考えてほしいと思っています。 MVP(最小価値製品)の考え方を全面的に取り入れる必要もあると思っています。 当社の全プロダクトが同じやりかたで実現できるとは思っていません。 自分たちでプロダクトの価値について深く考え、最短リリースを実現することが内製開発部隊の強みであり、これが最大の事業貢献だと思っています。 来年は徹底的にこれを突き詰めていきます。 ユーザーファーストと組織インテンシティ またAIファーストとリリースファーストを支える基礎として、来年は改めて「ユーザーファースト」と「組織インテンシティ」にも注力していきます。 ユーザーファースト ではサービスを使うユーザーだけでなく、ビジネスパートナーやその向こう側にいる顧客としっかり向き合うことを徹底していきたいと思っています。 ユーザーや顧客にとっての価値が何なのかを能動的に見つけられることは、われわれ内製開発の価値を高めることに繋がります。 そのためにはユーザーリサーチなどの方法論も武器として身に付ける必要もあります。 インテンシティ は サッカー用語 です。 優れた能力をもつメンバーが多く集まっていることはKINTOテクノロジーズの強みですが、内製開発部隊としてより高い成果を実現するためにはチームや組織のレベルをもう一段、二段と高めていく必要があります。 KTCをインテンシティの高い組織にするため、ひとりひとりがどのような心がけや行動をすべきか考え抜いてもらいたいと思います。 全力投球 次の段階に進むため2025年のテーマには入れなかった技術力と発信力の向上は社内に定着し、あたりまえになってきていると感じています。これからも各自が意識して、継続的に取り組んでいってほしいKTCの永遠のテーマです。 これらも含めて、来年も手を抜かないで全力投球していきます。 KINTOテクノロジーズ 取締役副社長 景山 均
アバター
はじめに VSCodeとCopilotの組み合わせが最高に楽しいです。楽しすぎて本業を忘れてしまいそうです。 本日はそんな楽しすぎる開発体験をみなさんにもぜひ知って欲しいという思いで記事を書きます。 この記事は KINTOテクノロジーズアドベントカレンダー2024 の25日目の記事です🎅🎄 今回の内容 去年辺りから熱くなったAI界隈ですが、今年もAIの話題が尽きない一年でしたね! KTCでは積極的にAIを取り入れて開発生産性低減の道を探っています。 今回はVSCode x Copilotの組み合わせでできることを紹介します。 でも、AIってなんか微妙なコードしか出してこないし、自分で書いた方が早いから使わないよね、という方もいるかもしれません。 僕も初めはそうでしたが、先日開催されたGitHub Universeで発表されたCopilot Chat/Editsを見て、AIとの共同作業が楽しくなるかもしれないと感じました。 と言うことで、今回は実際にCopilot Chat/Editsを使って、AIと共同でプログラミングをする中で、普段使いで有効だと思うTipsを紹介します。 https://reg.githubuniverse.com/flow/github/universe24/attendee-portal/page/livestream?tab.day=20241029 なんでFlutter? モバイル開発をする上で、iOSもAndroidも各OSの標準IDEを使って開発をするのが主流です。 しかし、今回はGitHub Copilotを使ってプログラミングするという観点で、VSCodeを使って開発することを前提とします。 そう考えると、モバイル開発で公式にVSCodeをサポートしているのはFlutterがメジャーです。 先日 FlutterKaigi にも参加し得るモノが多かったので、今回はFlutterを使ってAIとの共同作業をすることにします。ちなみに、FlutterKaigiのアプリ、本当に些細なIssueですが、僕もコントリビュートしました😀 実際に何ができるのか? GitHub Universeで発表された機能はPreview機能が多く、使用できる機能は限られていますが、すでにリリースされているCopilot Chat/Editsを使いこなすだけでも、AIとの共同作業が楽しくなり、開発者体験の向上が実感できました。 エンジニアの皆さんがAIを使ってコーディングするというと、ノーコードでアプリを作るとか、コードを自動生成するとか、そういったイメージがあるかもしれません。確かに、コードを自動生成する事は可能ですが、それはAIのほんの一部の機能でしかないです。 また、複雑な機能を持つコードを一度の指示で動作保証できるほどのコードを生成することは(指示出しが)難しいです。 この記事で紹介するのは、テストコードの作成、コードの整形・分割、リファクタリングなど、普段皆さんがコーディングでやっていることをAIとの共同作業を通じて行い、コードの品質を向上させることができるCopilot Chat/EditsのTipsになります。 それでは、ここからTipsの紹介です。 コードの要約 Copilot Chatの機能を使うと、コードの要約を簡単に作成できます。これを使えば、オンボーディング時のコード理解が早まります。 コメント記載 BEFORE AFTER コードにコメントを追加する際、Copilot Editsを使うと、Copilotが直接ファイルにコメントを追加してくれます。これにより、コードの理解が深まります。 また、すでにあるコメントを修正する際も、Copilotが適切なコメントを修正してくれます。 例えば上のクラスのaddメソッドに対して、バリデーションを追加した場合以下の様に指示するとコメントを更新してくれます。 BEFORE AFTER テストコードの作成 copilot editsのテキスト入力欄の上に現在のファイル以外に参照できるファイルを増やす事ができるので、テストコードを書いてもらうときは、 テスト用のファイルを作成し、そのファイルを開いたまま、参照ファイルに対象ファイルを追加すれば、テストコードを書いてくれます。 コードの整形・分割 Flutter等の宣言型UIを採用している言語でUIを作成しているとどうしてもネストが深くなってしまう問題が頻発します。 :::details 整形前のコード class SomethingPage extends StatelessWidget { const SomethingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Something Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(16.0), color: Colors.blueAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(8.0), color: Colors.redAccent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(4.0), color: Colors.greenAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(2.0), color: Colors.yellowAccent, child: const Text('Deeply Nested Widget 1'), ), Container( padding: const EdgeInsets.all(2.0), color: Colors.purpleAccent, child: const Text('Deeply Nested Widget 2'), ), ], ), ), ], ), ), ], ), ), ), ); } } ::: こういった問題もCopilot Editsを使うと、コードを見やすい単位で分割、整形してくれます。 :::details 整形後のコード class SomethingPage extends StatelessWidget { const SomethingPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Something Page'), ), body: Center( child: Container( padding: const EdgeInsets.all(16.0), color: Colors.blueAccent, child: const NestedContainer(), ), ), ); } } class NestedContainer extends StatelessWidget { const NestedContainer({super.key}); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ InnerContainer(), ], ); } } class InnerContainer extends StatelessWidget { const InnerContainer({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(8.0), color: Colors.redAccent, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ DeeplyNestedContainer(), ], ), ); } } class DeeplyNestedContainer extends StatelessWidget { const DeeplyNestedContainer({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(4.0), color: Colors.greenAccent, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [ DeeplyNestedWidget1(), DeeplyNestedWidget2(), ], ), ); } } class DeeplyNestedWidget1 extends StatelessWidget { const DeeplyNestedWidget1({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(2.0), color: Colors.yellowAccent, child: const Text('Deeply Nested Widget 1'), ); } } class DeeplyNestedWidget2 extends StatelessWidget { const DeeplyNestedWidget2({super.key}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(2.0), color: Colors.purpleAccent, child: const Text('Deeply Nested Widget 2'), ); } } ::: リファクタリング 歴史あるコードでよく見かける、依存度が複雑に絡み合ってしまったメソッドも、Copilot Editsを使うことで、SOLID原則に基づいたリファクタリングが可能です。 :::details 依存度が複雑なメソッドを持つクラス class SomethingLogic { Future<void> performComplexOperation() async { // Fetch data from remote server final response = await http.get(Uri.parse('https://example.com/data')); if (response.statusCode != 200) { throw Exception('Failed to load data'); } final data = jsonDecode(response.body); // Save data to local database final database = openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)', ); }, version: 1, ); final db = await database; await db.insert( 'data', {'id': data['id'], 'value': data['value']}, conflictAlgorithm: ConflictAlgorithm.replace, ); // Save data to shared preferences final prefs = await SharedPreferences.getInstance(); await prefs.setString('data', jsonEncode(data)); // Perform some complex logic if (data['value'] == 'special') { // ...existing code... print('Special value found!'); // ...existing code... } else { // ...existing code... print('Regular value found.'); // ...existing code... } } } :::: :::details SOLID原則に基づいたリファクタリングをしてもらったコード class DataFetcher { final http.Client client; DataFetcher(this.client); Future<Map<String, dynamic>> fetchData(String url) async { final response = await client.get(Uri.parse(url)); if (response.statusCode != 200) { throw Exception('Failed to load data'); } return jsonDecode(response.body); } } class DatabaseHelper { Future<Database> getDatabase() async { return openDatabase( join(await getDatabasesPath(), 'app_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE data(id INTEGER PRIMARY KEY, value TEXT)', ); }, version: 1, ); } Future<void> saveData(Database db, Map<String, dynamic> data) async { await db.insert( 'data', {'id': data['id'], 'value': data['value']}, conflictAlgorithm: ConflictAlgorithm.replace, ); } } class SharedPreferencesHelper { Future<void> saveData(Map<String, dynamic> data) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('data', jsonEncode(data)); } } class SomethingLogic { final DataFetcher dataFetcher; final DatabaseHelper databaseHelper; final SharedPreferencesHelper sharedPreferencesHelper; SomethingLogic(this.dataFetcher, this.databaseHelper, this.sharedPreferencesHelper); Future<void> performComplexOperation() async { final data = await dataFetcher.fetchData('https://example.com/data'); final db = await databaseHelper.getDatabase(); await databaseHelper.saveData(db, data); await sharedPreferencesHelper.saveData(data); if (data['value'] == 'special') { // ...existing code... print('Special value found!'); // ...existing code... } else { // ...existing code... print('Regular value found.'); // ...existing code... } } } :::: ここまでのリファクタリングを自分でやると、時間がかかりますが、Copilot Editsを使うことで、短時間でリファクタリングが可能です。 だいぶ見やすいコードになったので、ここから自身の経験をもとに、さらにリファクタリングを行う事も可能です。 その他 これ以外にも、基本的なSuggestionsの機能が強力で、普段であれば実装に困った際はブラウザとIDEを行き来していましたが、ほとんどの困りごとをCopilotとの相談で解決できるので、開発への集中が途切れなくなりました。これは地味に大きいです。 指示だしのコツ AIとの共同作業は、新しい発見がたくさんありますが、AIに対する指示だしが重要な要素だなと感じました。 僕の周りにもAIが使いづらく感じているエンジニアは多くいますが、AIに対して具体的な指示を出すことに慣れると、AIとの共同作業が楽しくなると思います。 僕が感じた指示だしのコツは シンプルな内容 具体的な内容 どのファイル 何をする どのようにする 一度に複数の指示を出さない 特に、指示を出す側がしっかりとしたプログラミング原則や設計思想を理解していることが重要だと感じます。 まとめ 今回はVSCode x Copilotの組み合わせによって、AIとできることを紹介しました。 Copilot Chat/Editsを使うことで、コードの要約、コメント記載、テストコードの作成、コードの整形・分割、リファクタリングが簡単にできる事が少しでも伝えられたら幸いです。 エンジニア界隈ではAIに対して賛否両論あり、 僕の周りのエンジニアもまだまだAIに対して抵抗がある人が多いです。 世間の流れは確実にAIに向かっていて、今後もAIは進化し、どんどんできることが増えていくことが予想されます。 僕らエンジニアはどうAIを使っていくかを考える時代になったのかなと感じでいます。 この記事が少しでもAIとの共同作業に対して興味を持っていただけたら幸いです。
アバター
Contents Introduction Target Audience Repository Setup Skeleton Batch Guide DB and CSV Batch Guide Continuous Integration Conclusion Introduction Hello, I'm Miyashita, an engineer from KINTO Technologies' Common Service Development Group[^1][^2][^3][^4][^5]. While developing batch processes with Spring Boot 3, I encountered several challenges with Spring Batch's class structure and annotations. The transition to Spring Boot 3 presented additional complexities, particularly with multi-database configurations and error handling. A particular challenge was the lack of Japanese documentation for Spring Boot 3. While referring to the official documentation, I had trouble finding configurations that matched my requirements and encountered issues with multi-database setup and error handling. This led to a cycle of trial and error, making me acutely aware of the challenges in building a batch process from scratch. In this article, I've compiled information to help others facing similar challenges implement Spring Batch more smoothly: Spring Boot 3 Batch Skeleton Immediate deployment through GitHub clone and run Ready for business logic implementation Pre-configured for production use Common Batch Processing Use Cases In addition to the skeleton, I'll introduce sample code for batch processing utilizing a multi-database configuration: Batch process for exporting DB records to CSV Batch process for importing CSV data to DB I hope this article helps make Spring Batch implementation and development easier. Target Audience This guide is designed for developers who are: ✓ New to Spring Batch 5 framework implementation ✓ Returning to Spring Batch after a hiatus ✓ Migrating from Spring Boot 2 Batch due to end of support ✓ Experienced with Java main method batches but need Spring Batch ✓ Seeking rapid batch development solutions Repository Setup https://github.com/kinto-technologies/SpringBoot3BatchStarter/ This is my repository. Please clone it and import it into your IDE. For GUI-based operations, use GitHub Desktop, Repository Structure The repository is structured as follows: . ├── gradlew # Gradle wrapper for build automation ├── settings.gradle ├── compose.yaml # Docker Compose configuration ├── init-scripts # Database initialization │ ├── 1-create-table.sql │ └── 2-insert-data.sql ├── dbAndCsvBatch # DB/CSV processing implementation │ ├── build.gradle │ └── src │ ├── main │ └── test └── skeletonBatch # Basic batch implementation ├── build.gradle └── src ├── main └── test Skeleton Batch Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/skeletonBatch This is a skeleton code that becomes a complete batch process just by adding business logic. It consists of only the necessary code components. Core Concepts: Jobs and Steps Spring Batch is built on two fundamental concepts: Jobs and Steps. Jobs A Job represents the complete batch process: Contains one or multiple Steps Manages batch execution flow Supports sequential and parallel processing graph TD A[Job] --> B[Step 1] A --> C[Step 2] A --> D[Step 3] style A fill:#f9f,stroke:#333,stroke-width:4px Job Capabilities Capability Implementation Example Sequential Chain multiple jobs Daily data processing pipeline Error Handling Define recovery actions Automated error notifications Concurrent Run multiple jobs simultaneously Parallel data processing Parallel Multiple instances of same job Large dataset processing Step A Step represents a processing unit within a job. One job can register multiple steps, and step behavior can be designed flexibly like jobs. Implementation Method Steps can be implemented using either Chunk or Tasklet processing. 1. Chunk Processing This is a method for efficiently processing large volumes of data. graph LR A[Reader] -->|100 items at once| B[Processor] B -->|One by one| C[Writer] C -->|100 items at once| D[DB/File] Implementation Characteristics Processing is divided into three phases: Reader: Reads data in fixed quantities Example: Reading 100 lines at a time from CSV file using FlatFileItemReader Processor: Processes data one item at a time Example: Converting date formats using CustomProcessor Writer: Outputs data in bulk Example: Bulk INSERT to DB using JdbcBatchItemWriter Transactions are committed by chunk size (e.g., 100 items). This means that even if an error occurs midway, the successfully completed chunks are committed. 2. Tasklet Processing This is a method for executing simple, single-unit processes. Characteristics Simple implementation Easy to understand processing flow Straightforward debugging Choosing Between Implementation Methods Aspect Chunk Tasklet Data Volume Suitable for large volumes Suitable for small volumes Processing Complexity Suitable for complex processing Suitable for simple processing Management Cost Higher Lower Debugging Somewhat complex Easy Transaction By chunk By process unit Summary Job is the management unit for the entire batch process Step is the execution unit for specific processes Chunk is suitable for efficient processing of large data volumes Tasklet is optimal for simple processing Choose between Chunk and Tasklet based on processing requirements :::message Key Point For simple batches that don't require complex processing, choose Tasklet without hesitation. This skeleton batch also adopts Tasklet processing. ::: :::message alert Note In official documentation and many web articles, chunk processing is explained as the basic approach of Spring Batch, which often leads beginners to mistakenly think they must use chunks. This frequently results in implementing with chunks first, then realizing later that Tasklet would have been more appropriate, causing rework. ::: YAML Configuration and Spring Batch Management Tables Let's first explain the basic configuration needed to run Spring Batch in batch-dedicated mode. YAML Configuration https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/resources/application.yml This configuration allows the Spring Boot application to run in serverless mode. While typical Spring Boot applications maintain their process by starting servers like Tomcat, this is unnecessary for batch processing. With this configuration, the process terminates when batch processing completes. Spring Batch Management Tables Spring Batch requires management tables in a database to record execution results. However, managing databases and tables can be cumbersome. Moreover, you might need to run batches in environments without databases. In such cases, using H2 in-memory database is recommended. When the process ends: Memory is released Database and tables are cleared Next execution starts with a fresh state H2 is automatically configured if no database settings are specified in application.yml :::message Benefits of Persistent Storage While using H2 is convenient for development, using a persistent database allows you to: Maintain execution history Resume from interruptions Track batch execution status ::: Job Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/job/SampleJob.java#L16-L41 This is the core class for registering and executing Jobs in Spring Batch. Class Definition Key Points @Configuration Recognized as a Spring Batch configuration class Indicates that this class provides Spring configuration Required annotation for Job definition classes @Bean Applied to methods that generate objects managed by Spring Framework In this case, allows Spring to manage the Job created by createSampleJob The Job instance is used during batch execution :::message alert Important Note By default, the method name is used as the Bean name. If you copy this class to create a new Job, using the same method name will cause an error. To avoid this, specify a unique name like @Bean("jobName") . This is a common pitfall to watch out for. ::: Dependency Class Roles JobRepository : Manages job execution state PlatformTransactionManager : Maintains database consistency SampleLogic : Handles actual business processing Processing Flow Output job registration start log Create Step (define processing unit) Create Job and register Step Output job registration completion log Transaction Management Normal completion: Database changes are confirmed when all processing succeeds Abnormal termination: Database changes are rolled back when errors occur This ensures the reliability of batch processing operations. Logic Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/logic/SampleLogic.java#L13-L35 This class defines the actual processing content of the batch. Class Definition Key Points @Component Makes this class manageable by Spring Allows usage with @Autowired in other classes Tasklet Interface Interface representing Spring Batch processing unit Implement actual processing in the execute method Processing Flow Output batch processing start log Call SampleService's process method Output completion log on normal termination Log exception and re-throw on error occurrence Output batch processing end log Error Handling Normal completion: Return RepeatStatus.FINISHED to complete batch Abnormal termination: Catch exception, log it, and notify upper layer :::message Implementation Point While this class delegates processing to SampleService, you can implement simple processing directly in the execute method. ::: Service Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/skeletonBatch/src/main/java/com/example/batch/service/SampleService.java#L8-L23 This class is for implementing actual business logic. Class Definition Key Points @Service Recognized as a Spring Framework service class Indicates this class implements business logic process Method Method for implementing batch business processing Includes start and completion log output :::message Implementation Point This class is completely independent from Spring Batch classes and interfaces. This means: Focus purely on business logic implementation Easy to write JUnit tests Easily reusable in other projects ::: Customization Options You can modify: Class name Method name Arguments and return values Business logic implementation (e.g., data validation, transformation, external system integration) This concludes our explanation of the main skeleton batch classes. This loosely coupled design makes the batch easy to maintain and test. Running the Batch Running from IDE Start the BatchApp class. It works like a regular Spring Boot application with no special startup arguments required. Running via Gradle from Terminal Execute the following command: ./gradlew :skeletonBatch:bootRun Running by Generating and Executing JAR File Gradle's default task is configured to generate a JAR file. Execute as follows: cd skeletonBatch ../gradlew java -jar build/libs/batch-skeleton*.jar Checking Execution Logs Let's examine the Spring Batch execution flow through the logs. 1. Job Registration Check ----------- Registering job: sample ----------- ----------- Job registered successfully: sample-job ----------- During Spring Boot startup, the batch job 'sample-job' was successfully registered. 2. Batch Processing Execution Started BatchApp in 0.456 seconds (process running for 0.616) Running default command line with: [] Job: [SimpleJob: [name=sample-job]] launched with the following parameters: [{}] Executing step: [processing-step] ----------- START ----------- Batch Processing ----------- --- Starting batch process --- --- Batch process completed --- Processing completed successfully ----------- END ----------- Batch Processing ----------- Step: [processing-step] executed in 3ms Job: [SimpleJob: [name=sample-job]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 9ms Important Points from Logs: 'sample-job' executed and 'processing-step' started START and END markers clearly show processing boundaries Step completed successfully and job finished with COMPLETED status Application terminated normally Skeleton Batch Summary Advantages Complete batch process by just adding business logic Integration with schedulers like cron using generated JAR file High maintainability through simple structure :::message Development Support Spring Batch development often faces issues like: Missing annotations Configuration mistakes Complex error messages This skeleton helps you avoid these problems and focus on implementing business logic. ::: This skeleton code aims to contribute to more efficient batch development. DB and CSV Batch Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/tree/main/dbAndCsvBatch Overview This project includes two batch processes: DB to CSV Export Exports database records to CSV format Customizable extraction conditions via startup arguments Works with default settings CSV to DB Import Bulk imports CSV data into database Requires CSV file, so run export batch first Local Database Setup This batch uses a MySQL local database. Follow these steps for setup: Start MySQL Container docker compose up -d :::message If Docker is not installed, download and install it from the Docker official website . ::: Verify Setup Connect to MySQL container and check sample data: docker exec -it mysql-container mysql -u sampleuser -psamplepassword sampledb Run sample query: mysql> SELECT * FROM member WHERE delete_flag = 0 AND type IN (1, 2, 3) ORDER BY type ASC; mysql> exit Bye Entity Class Auto-generation After cloning the GitHub repository, you might encounter a "Entity class not found" error. This occurs because jOOQ needs to auto-generate Entity classes. Running Auto-generation Either command will generate Entity classes: # Execute default task cd dbAndCsvBatch/ ../gradlew # Or directly execute jOOQ generation ../gradlew generateJooq The build.gradle is configured for immediate use without additional settings. Default task execution includes: Cleanup of build results Java code formatting (Google Java Format) Entity class auto-generation (jOOQ plugin) Compilation Static analysis and testing (JUnit, coverage, SpotBugs) Executable JAR generation Generated Entity Classes Check generated Entity classes with: tree dbAndCsvBatch/build/generated-src # If tree command is not installed: ls -R dbAndCsvBatch/build/generated-src Example output: dbAndCsvBatch/build/generated-src/jooq └── main └── com └── example └── batch └── jooq ├── DefaultCatalog.java ├── Keys.java ├── Sampledb.java ├── Tables.java └── tables ├── Member.java └── records └── MemberRecord.java :::message alert Since jOOQ plugin auto-generates code from database schema during build, managing it with Git can cause conflicts when schema changes. Therefore, generated code should not be included in Git management. ::: Handling "Class Not Found" Errors Your IDE might report Entity classes not found. Resolve this by either: Add generated-src/jooq to IDE's build path Copy classes from generated-src/jooq to dbAndCsvBatch/src/main/java Production Environment Setup https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/build.gradle#L101-L124 :::message Change connection settings to your DEV/STG database Or add your production CREATE TABLE statements to Docker Compose initialization SQL ::: jooq gradle plugin website Multi-database Configuration This batch uses a dual database configuration: H2 (In-memory Database) Used for Spring Batch management tables Resets when process ends Ideal for development and testing MySQL (Business Logic Database) Used for actual business data processing Runs in Docker container Configuration File (application-local.yml) Spring Boot's configuration file defines two databases under spring.datasource. https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/application-local.yml The configuration file includes: Two database settings Profile suffixes (local and server) Runtime profile selection capability The server profile is intended for your production MySQL server settings. Data Source Configuration Class https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/config/DataSourceConfig.java#L11-L37 Key Annotations @Configuration Marks class as Spring configuration Spring manages beans defined here @ConfigurationProperties Maps YAML settings to class properties Example: maps 'spring.datasource.mysqlmain' @BatchDataSource Specifies data source for Spring Batch tables Applied to H2 database @Primary Designates default data source Applied to MySQL data source Job Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/job/DbToCsvJob.java#L31-L39 New Features Added Automatic Run ID Increment .incrementer(new RunIdIncrementer()) Automatically increments job execution number (run.id) Used for execution history management :::message While this sample uses H2 (in-memory DB) making the run ID transient, this is a standard implementation pattern when using persistent databases. ::: Listener Integration .listener(listener) Add processing before and after job execution Useful for logging, email notifications, etc. Logic Class Guide https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/logic/DbToCsvLogic.java#L18-L49 This example demonstrates basic logging implementation, but you can add custom processing like error handling and notifications. Parameter Configuration @Value("${batch.types:2,3,4}") private String typesConfig; Uses 2,3,4 as default values even without application.yml Can be overridden by passing --batch.types=1,2 at runtime Error Handling contribution.setExitStatus(ExitStatus.FAILED); Explicitly sets failure status on error Automatically sets COMPLETED status on normal completion Repository Class and jOOQ Guide About jOOQ jooq is a library that allows writing SQL as Java code: Express SQL syntax directly in Java code Auto-generate Entity classes from table definitions Type-safe SQL operations Select Processing https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L18-L25 Key Features SQL-like Syntax Direct expression of SQL in Java code Leverage existing SQL knowledge Auto-generated Class Usage MEMBER: Represents table definition (column names, etc.) MemberRecord: For record mapping Bulk Insert Processing https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/java/com/example/batch/repository/MemberRepository.java#L54-L69 Bulk Insert Features Registers multiple records at once instead of one-by-one Common in batch processing to reduce database load :::message While not used in this batch, example code for insert, update, and delete operations is included. You can quickly adapt the repository class by replacing table and column names to match your business environment. ::: Repository Class Summary Use jOOQ's Gradle plugin to auto-generate entities from database schema Auto-generated classes enable type-safe SQL writing in Java Easy to handle schema changes - just regenerate classes :::message Spring Batch supports multiple database operation methods: Hibernate (JPA) Powerful database abstraction Easy Spring Boot integration May have overhead with large datasets MyBatis Ideal for direct SQL management Strong dynamic query support May reduce maintainability Spring JdbcTemplate Lightweight database operations Good for simple queries Can be verbose ::: :::message alert Note Choose the appropriate method based on: Project requirements Team skill set Direct SQL execution if needed ::: DB to CSV Batch Execution Guide Run the DB to CSV batch following similar steps as the skeleton batch. Running via Gradle ./gradlew :dbAndCsvBatch:bootRun Running JAR File cd dbAndCsvBatch/ ../gradlew java -jar build/libs/batch-dbAndCsv*.jar Running without arguments produces this error: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobLauncherApplicationRunner' defined in class path resource [org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.class]: Job name must be specified in case of multiple jobs at com.example.batch.DbAndCsvBatchApp.main(DbAndCsvBatchApp.java:16) Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs Why This Error Occurs Unlike the skeleton batch with a single job, this project contains multiple jobs: CSV to DB: Importing CSV data into database DB to CSV: Exporting database data to CSV The framework cannot determine which job to execute without explicit specification. Running with Arguments Specify the job name and environment in startup arguments: --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local --spring.batch.job.name=DB_TO_CSV: Specifies which job to run --spring.profiles.active=local: Activates local environment settings Example Commands # Using Gradle ./gradlew :dbAndCsvBatch:bootRun --args="--spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" # Using JAR directly cd dbAndCsvBatch/ java -jar build/libs/batch-dbAndCsv*.jar --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local Examining the Logs Let's check the execution logs: 1. Initial Startup Log ##### KEY:"sun.java.command", VALUE:"com.example.batch.DbAndCsvBatchApp --spring.batch.job.name=DB_TO_CSV --spring.profiles.active=local" ##### Spring Batch ##### - Job: DB_TO_CSV, Profile: local This confirms our startup arguments were correctly received. 2. Job Start Log ----------- JOB [Job Name:DB_TO_CSV] START! ----------- Executing step: [DB_TO_CSV-step] Fetching members with types = [1, 2, 3] Shows BatchNotificationListener's beforeJob method execution and configured types. 3. Database Operation Log -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (1, 2, 3)) order by `sampledb`.`member`.`type` Version : Database version is supported by dialect MYSQL: 9.1.0 Fetched result : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | id|type|name |email |phone |address |delete_flag|created_at |updated_at | : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ : | 1| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 2| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : | 26| 1|John Doe |john.doe@example.com |1234567890|123 Main St, City, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 27| 1|Jane Smith|jane.smith@example.com|0987654321|456 Oak St, Town, Country | 0|2024-12-09T05:36:37|2024-12-09T05:36:37| : | 3| 2|ABC Corp |contact@abccorp.com |5678901234|789 Pine St, Village, Country| 0|2024-12-07T09:46:07|2024-12-07T09:46:07| : +----+----+----------+----------------------+----------+-----------------------------+-----------+-------------------+-------------------+ Fetched row(s) : 5 (or more) Batch process completed successfully. Step: [DB_TO_CSV-step] executed in 193ms Job: [SimpleJob: [name=DB_TO_CSV]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 212ms ----------- JOB [Job Name:DB_TO_CSV] FINISHED! status:[COMPLETED] ----------- https://github.com/kinto-technologies/SpringBoot3BatchStarter/blob/main/dbAndCsvBatch/src/main/resources/logback.xml#L21-L22 jOOQ's query and result logging is enabled in logback.xml for easier debugging. Generated CSV Check "id","type","name","email","phone","address","deleteFlag","createdAt","updatedAt" "1","1","John Doe","john.doe@example.com","1234567890","123 Main St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "2","1","Jane Smith","jane.smith@example.com","0987654321","456 Oak St, Town, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "3","2","ABC Corp","contact@abccorp.com","5678901234","789 Pine St, Village, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "5","3","Alice Premium","alice.premium@example.com","4561237890","987 Maple St, City, Country","0","2024-12-11T06:05:26","2024-12-11T06:05:26" "6","3","Charlie Davis","charlie.davis@example.com","1112223333",,"0","2024-12-11T06:05:26","2024-12-11T06:05:26" The CSV file is generated using the OpenCSV library. opencsv website Testing Runtime Argument Override Let's try customizing the types parameter at runtime: --spring.batch.job.name=DB_TO_CSV --batch.types=4,5 --spring.profiles.active=local Log Output -> with bind values : select `sampledb`.`member`.`id`, `sampledb`.`member`.`type`, `sampledb`.`member`.`name`, `sampledb`.`member`.`email`, `sampledb`.`member`.`phone`, `sampledb`.`member`.`address`, `sampledb`.`member`.`delete_flag`, `sampledb`.`member`.`created_at`, `sampledb`.`member`.`updated_at` from `sampledb`.`member` where (`sampledb`.`member`.`delete_flag` = 0 and `sampledb`.`member`.`type` in (4, 5)) order by `sampledb`.`member`.`type` This confirms that the configuration file values were successfully overridden by command line arguments. CSV to DB Batch Execution Run the batch with the specified job name: --spring.batch.job.name=CSV_TO_DB --spring.profiles.active=local Log Output insert into `sampledb`.`member` (`name`, `email`, `phone`, `address`, `type`) values ('Premium Corp', 'premium@corporate.com', '8889997777', '555 High St, City, Country', 4), ('Elite Ltd', 'elite@elitecorp.com', '4445556666', '777 Sky Ave, Town, Country', 4), ('Guest User1', 'guest1@example.com', '', 'Guest Address 1, City, Country', 5), ('Guest User2', 'guest2@example.com', '9998887777', '', 5) Affected row(s) : 4 This confirms successful bulk insertion of records. Continuous Integration This project implements CI (Continuous Integration) using GitHub Actions. Quality management is efficiently handled through automated processing triggered by code pushes or pull requests. Main Workflow MySQL Setup Launch MySQL using Docker Compose Verify required tables JDK 21 Setup Install Java 21 Configure build environment jOOQ Class Generation Auto-generate entity classes from database schema Build and Test Execute Gradle build Run quality checks: JUnit tests Jacoco coverage SpotBugs analysis This workflow enables: Immediate detection of compilation errors Quick identification of JUnit test failures Rapid response to issues Consistent code quality maintenance Dynamic Badge Updates When the build and tests succeed, a dynamic badge is displayed on the GitHub README, indicating the status of the project. Code Coverage Visualization This project uses Codecov to measure and visualize test coverage. Coverage reports are automatically generated during pull requests, and the coverage rate can be checked via this badge: graph LR D[👩‍💻 Developer] -->|Push/PR| G[🐙 GitHub] G -->|Trigger| GHA[⚡ GitHub Actions] GHA -->|Build and Test Results| R[📄 README.md] GHA -->|Coverage Report| C[☁️ Codecov] C -->|Coverage Badge| R style G fill:#f4f4f4,stroke:#171515 style C fill:#f01f7a style R fill:#e6e6e6 This enables: Visual tracking of test coverage Quick detection of coverage changes Enhanced transparency in quality management Detailed coverage reports are available on the Codecov dashboard. Conclusion We hope you found this guide helpful! This project provides: Skeleton code foundation for efficient Spring Batch development Common use cases like "DB to CSV" and "CSV to DB" Flexible customization through database settings and CSV layouts We hope this skeleton helps streamline your batch development by allowing you to focus on business logic implementation. If you found this article helpful and got your Spring Batch up and running quickly, we would appreciate a ⭐ on our GitHub repository! Thanks for reading! [^1]: Post by Common Service Development Group Member 1 [ Implementing Domain-Driven Design (DDD) in Payment Platform with Global Expansion in Mind ] [^2]: Post by Common Service Development Group Member 2 [ Success Story: New System Development through Remote Mob Programming by Team Members with Less Than One Year Experience ] [^3]: Post by Common Service Development Group Member 3 [ Improving Deploy Traceability Across Multiple Environments Using JIRA and GitHub Actions ] [^4]: Post by Common Service Development Group Member 4 [ Development Environment Setup Using VSCode Dev Container ] [^5]: Post by Common Service Development Group Member 5 [ Guide to Setting Up S3 Local Development Environment Using MinIO (AWS SDK for Java 2.x) ]
アバター
この記事は 技術広報カレンダー2024 の23日目の記事です🎅🎄 はじめに こんにちは!リナ( @chimrindayo )です。 KINTOテクノロジーズで、エンジニアとして モビリティマーケット の開発運用と技術広報を兼務しています。 さて、KINTOテクノロジーズは2022年7月にテックブログを開設し、執筆者及び読者のみなさんのおかげで今日までテックブログが運営できています。いつもありがとうございます! 中でもアドベントカレンダーは、テックブログの一大イベントです🎄 そんな一大イベントで、今年はついに シリーズ4まで計100記事 を投稿できました👏(すごい) こうして100記事リリースできるようになるまで、技術広報のメンバーはさまざまな工夫に日々取り組んできました。今回はアドベントカレンダーを100記事リリースできるまでの軌跡と工夫の一部をご紹介したいと思います。 KINTOテクノロジーズ アドベントカレンダーの軌跡 KINTOテクノロジーズのアドベントカレンダーは、有志のメンバーによってテックブログ開設前の2021年に開始しました。そして翌年、2022年4月にテックブログ運用プロジェクト(現:技術広報グループ)という有志のチームが発足し、2022年7月にテックブログを開設しています。 テックブログ開設以降は毎年欠かさずにアドベントカレンダーを投稿し、25記事ずつ投稿数を増やしています⤴️ 年 URL 投稿数 2021 KINTO Technologies - トヨタ車のサブスク「KINTO」開発中! Advent Calendar 2021 24 2022 4月:テックブログ運用チーム発足 7月:テックブログ開設🎉 - 2022 KINTOテクノロジーズ Advent Calendar 2022 KINTOテクノロジーズ グループ紹介 Advent Calendar 2022 50 2023 KINTOテクノロジーズ Advent Calendar 2023 75 2024 KINTOテクノロジーズ Advent Calendar 2024 100 では私たちが記事を増やすためにどんな工夫に取り組んできたのか、各年ごとにふりかえっていきます。 テックブログ開設初期〜継続期で各フェーズに合わせて工夫を凝らしているため、これからテックブログを開設する or したい方、現在テックブログを運用しているが執筆者が増えなくて悩んでいる方のご参考になれば嬉しいです🙌 2022年 アドベントカレンダーの工夫 まずはじめに、テックブログ開設初期の工夫点です。 ふりかえってみると開設初期は、テックブログを執筆してもらうために 各個人にアプローチ をしていたのが大きな特徴だと思います。では具体的な内容をご紹介します。 執筆者への感謝のスタンスをルールにする まずはじめにやったことが「執筆者への感謝」をレビュアーのスタンスとしてルールにすることです。 このルールは 中西さん を中心に有志のメンバーで決めていきました。 そして、レビュアーになり得るマネージャーやリーダーに全社会議や1on1などの場で依頼したり、テックブログのPRテンプレートに書くことで執筆者に感謝するマインドを伝え続けています。 テックブログのPRテンプレートから抜粋 今でも執筆に感謝するということは、技術広報チーム全員が大切にしています。 全てのマネージャーに記事を書いてもらう 次にやったことは、各部署のマネージャーに記事を書いてもらうことです。 テックブログ開設初期は、「テックブログって何?どうしてやるの?」と思っている社内のメンバーも少なくありませんでした。よって、まずは各グループをリードしているマネージャー1人1人にテックブログの必要性を理解してもらうこと、そして全社にテックブログを広めるためには上位レイヤーの人から執筆する必要があると考え、マネージャーのみなさんと30分ほどのミーティングをしたうえで実現したのが「KINTOテクノロジーズ グループ紹介」です。 https://qiita.com/advent-calendar/2022/kinto-technologies-introduction 「社員・オフィスの様子をみんなに知ってもらう」をテーマに、各グループのマネージャーおよびリーダがアドベントカレンダーを執筆しました。 クリスマスには副社長の景山さんが「2022年振り返り&2023年展望」と言う記事で締めくくっています🎅 2022年以降クリスマスは景山さんの記事が毎年恒例になり、採用候補者の方や社員がほぼ全員目を通しています。 KINTOテクノロジーズが どのような組織で この1年どんなことをやってきたか 今後1年どんなことをやっていくのか がこの記事に凝縮されていて、組織の道しるべとなるような記事です。 景山さんが年に1度力を入れて執筆しています。 振り返り&展望の記事はこちら👇 https://blog.kinto-technologies.com/posts/2022-12-25-LookBack2022/ 記事の構成を一緒に考える 主に記事の執筆意欲はあるが技術記事を執筆した経験がなく、何を書いたらいいか相談したい人向けに30分程度で記事の構成を一緒に考えるミーティングを実施しています。 技術広報のメンバーが執筆者にインタビューする形式で 今までどんな業務をしていたのか 各プロジェクトの失敗/成功談と改善策 技術スタック などをヒアリングして、技術広報のメンバーがインタビュー内容を聞きながらその場で記事の構成を組み立てていきます。一通りインタビューが終わったら、作成した記事の構成をその場で執筆者の方に見ていただきます。すると、ほとんどの人に「思ったより簡単書けるかも!」と言っていただけます。 2022年のテックブログを開設した当時は、この取り組みを執筆者全員に行っていました。 (今考えたら力技すぎる...!ですが全員とコミュニケーションを取るこの力技こそが、私たちKINTOテクノロジーズの技術広報の強みであると考えています。よくこの話を他社の技術広報の方に共有すると「素晴らしい取り組み」と評価をいただけることが多いです。) 全員に行っていた理由はいくつかあります。 主な理由は、 テックブログの必要性をみんなに理解してもらいたい テックブログを書くことのハードルを下げたい 誰が何の業務を担当しているのか知りたい 気軽になんでも相談してもらえる関係を築く などです。 しかし、最も大事だと考えているのが、みなさんの 業務に価値があると再認識 してもらうことです。 実際に当時みなさんと対面でミーティングをした時に「書いてもいいけど、自分の業務は普通のことだからブログにするほどじゃないよ」と言う人が多くいました。 いやいや普通でいいんです! 絶対に同じように悩んでいる人が世界に1人か2人いて、その人に届けばいいんです。 誰か1人に届けば、誰か1人が面白いと思えば、十分にあなたの業務をテックブログ発信する価値があります。 だから一緒に執筆してみましょう!! というのを執筆者1人1人に伝え続けました。 この考えは現在でも技術広報チームが大事にしており、先日リリースされたブログでもご紹介しています👇 https://blog.kinto-technologies.com/posts/2024-12-11-gekishin/ このように記事の構成を一緒に考えるだけでなく、執筆者がこれから書こうとしている記事の内容に自信を持ってもらうことが重要であると考えています。 2023年 アドベントカレンダーの工夫 テックブログの開設初期は、1人1人と会話することで「まずはテックブログを書いてもらう」ための工夫を中心に取り組んできました。 次に2023年はいかにテックブログを 継続 して書いてもらうかに着目し、さらに執筆者を増やすための取り組みを実践しています。 インプットを支援する 2023年まず技術広報のメンバーで実施したことは、 Udemy Business の導入支援です。 なぜ導入を支援したかというと、そもそもテックブログへのアウトプットは知識やスキルのインプットがなければできないと考えているためです。したがって、Udemyのアカウント登録条件を「テックブログを書くこと」にしています。 実際にUdemyを受講して記事を書いているメンバーがいます👇 https://blog.kinto-technologies.com/posts/2024-08-30-udemy-kotlin-coroutines-and-flow/ またその後2024年にUdemyの導入支援以外に技術広報で知識のインプットからアウトプットまで包括的にサポートできるように「学びの道の駅」というチームが発足しています。 学びの道の駅チーム発足の経緯はこちら👇 https://blog.kinto-technologies.com/posts/2024-04-23_%E5%AD%A6%E3%81%B3%E3%81%AE%E9%81%93%E3%81%AE%E9%A7%85%E3%81%AF%E3%81%98%E3%82%81%E3%81%BE%E3%81%97%E3%81%9F/ 入社エントリを書いてもらう そして2023年10月から入社エントリを開始しました。 これから入社する人に会社の雰囲気をわかってもらえたら嬉しい テックブログへのアウトプットへの抵抗がなくなってほしい 入社同期の横の繋がりを作れたら嬉しい という思いから、入社エントリの記入をお願いしています。 入社時に誰もがテックブログを書くことで、テックブログの書き方や書くこと自体のハードルを下げること、そしてテックブログを書くことが当たり前になることを願っています。 https://blog.kinto-technologies.com/posts/2024-01-01-newcomers-introduction/ また、この入社エントリは入社メンバー1人1人に執筆してもらうとして技術広報メンバーで企画を進めていたのですが、10月入社の Ryommさん が「共同執筆したい!」と提案してくれたのをきっかけに1記事を共同で執筆する流れができました。 入社エントリの例のように技術広報グループのメンバーだけで企画を決めて進行するだけでなく、社員全員でアウトプットするカルチャーを創りあげています。 チーム単位で執筆を依頼する 次に、アドベントカレンダーをプロジェクト・部署単位のチームで執筆してもらう企画を実践しました。 5グループ・5名ずつ、計25日分執筆しています。 新車サブスクサイトのリニューアル アジャイル開発 KINTO FACTORY QA Diversity & Inclusion テックブログを日頃業務をともにしているチームのメンバーみんなで書くことによって、執筆者がテックブログのレビュー依頼や執筆の相談がチーム内でよりやりやすくだろうと考えたためです。 そしてチームのメンバーと一緒に、また共通のトピックで記事を執筆したことは、テックブログは読み手である社内のメンバーにとっては身近なコンテンツになるため、テックブログを読むきっかけになったり、SNSでブログを共有するきっかけになると考えました。 実際の企画はこちらのブログで紹介しています👇 https://blog.kinto-technologies.com/posts/2023-11-20-advent-calendar/#2.-%E8%A8%98%E4%BA%8B%E3%83%AA%E3%83%AC%E3%83%BC 2024年 アドベントカレンダーの工夫 最後に今年実践した工夫を紹介します。2024年はさらなる テックブログの拡張と品質向上 をテーマに取り組んできました。 テックブログの勉強会を開催する 2024年8月に技術広報のメンバーである p2skさん による社内向けのテックブログの勉強会を実施しました。 「テックブログの振り返りと発信力向上のためのベストプラクティス」というテーマで、KINTOテクノロジーズのテックブログ全記事を読んだ上で、Goodポイントや記事をよりよくするためのアイディアを共有しています。 社内の勉強会参加者からは「モチベーションが上がった!」「記事が書いてみたくなった」「アドベントカレンダー頑張ろう!」などの声があがり大変好評でした。 勉強会の内容は、非常に濃く本記事では割愛します。 企画運営を公募する 例年は技術広報のメンバーでアドベントカレンダーを企画・リードしていたのですが、今年は有志のメンバーを募集しました。狙いは、技術広報グループ以外の新しいメンバーに運営に入ってもらうことで、技術広報が今までアプローチできていなかったメンバーにリーチすることです。 ![](/assets/blog/authors/rina.k/100article/member.png =600x) 最大5名ほど集まったら嬉しいなと思っていたのですが、手を挙げてくれたのは1名でした。 その手を挙げてくれた1名が naka_shimaさん です。 naka_shimaさんが挙手してくれたことがきっかけで後述の部署単位でのシリーズの追加に繋がったと思います👏 部署単位でシリーズを埋める 2024年はグループ(部署)単位でシリーズを埋めました。 シリーズというのは、1~25日のアドベントカレンダー全日程 計25記事投稿することを指しています。 対象となったグループは、運営として挙手してくれたnaka_shimaさんが所属するモバイルグループと技術広報グループです。 モバイルグループ 技術広報グループ まずモバイルグループが選出された理由は、今年最も積極的に情報発信を行なっていたグループだからです。iOSDC2024やDroid Kaigiなどのスポンサーイベントの企画運営はもちろん、日頃のテックブログの発信も自発的に行なっていたのが特に目立っていたグループです。そこで「もしかしたら、1~25日全部モバイルグループで埋められるんじゃない・・・?」という無茶振りのもと実現したのが、モバイルグループで1シリーズです🎉 次に技術広報グループで1シリーズ実施することになった理由は、情報発信をリードするチーム自身が積極的にテックブログでのアウトプットを行う姿を見せることが重要だと考えたからです。 こうして ランダムテーマ(有志のメンバーが自由に執筆できる) モバイルグループ 技術広報グループ 翻訳(ローカライゼーションチームによる英日翻訳記事) の計100本をリリースすることができたのです。 今後の展望 テックブログを開設した2022年から2024年現在に至るまでの3年間の工夫をアドベントカレンダーを通してご紹介しました。みなさんのご協力とこれまでの工夫が2024年のアドベントカレンダー100本リリースに繋がっていると思います! 2025年は 執筆のしやすさ 執筆のモチベーション維持 を中心に工夫に取り組んでいきたいと思います。 2024年もありがとうございました☺️
アバター
こんにちは( º∀º )/ 技術広報G イベントチームのゆかちです。 2024年、カンファレンスのスポンサー活動等も開始し外部発信活動が活発になり、 以前に比べて社内外どちらもイベントがたくさん増えました…! 社外向けイベントを開催していて、エントランスまでの行き方を伝えるのが難しいな~とモヤモヤしておりました。 「写真で説明したい…はっテックブログがある…!」と思い立った先の記事デス。 オフィスについて 弊社、拠点が4つあります! そういえば2年前にオフィス紹介の記事を書いていた…! @ card そんな中でイベント会場になることが多いのが、 室町オフィスの TOKYO JCT です。 コレド室町2のオフィス棟16階にあります! ![TOKYO JCT](/assets/blog/authors/uka/access/jct.jpg =600x) ** 住所:〒103-0022 東京都中央区日本橋室町2丁目3−1 室町古河三井ビルディング(COREDO室町2) 東京メトロ銀座線『三越前』駅直結 ※改札でて徒歩 2 分 東京メトロ半蔵門線『三越前』駅直結 ※改札でて徒歩 5 分 JR 総武快速『新日本橋』駅直結 ※改札でて徒歩 5 分 JR 中央線・京浜東北線・山手線『神田』駅徒歩 10 分 ではでは各駅からのアクセス方法を写真にて紹介します! アクセス案内へのショートカット 東京メトロ銀座線『三越前』駅 東京メトロ半蔵門線『三越前』駅 JR総武快速『新日本橋』駅 JR中央線・京浜東北線・山手線『神田』駅 東京メトロ銀座線『三越前』駅 まずは一番近い銀座線から! ![銀座線からオフィスへの道案内1](/assets/blog/authors/uka/access/gin1.png =600x) 三越方面改札をでて左へ ![銀座線からオフィスへの道案内2](/assets/blog/authors/uka/access/gin2.png =600x) コレド室町1があるので通り抜ける ![銀座線からオフィスへの道案内3](/assets/blog/authors/uka/access/muro.png =600x) 通り抜けた先にコレド室町2の入口があります ![銀座線からオフィスへの道案内4](/assets/blog/authors/uka/access/es.png =600x) はいってエスカレーターを上って… ![室町オフィスエントランス1](/assets/blog/authors/uka/access/en1.png =600x) すぐにみえるオフィスエリアになります! :::message イベント受付時間内はオフィスエリアのエントランスに受付が立っています! コレド室町2からオフィスエリアへは入館カードがないと入れないため、受付時間内は来場者っぽいかたを見つけたら扉を開けております。 ::: イベントではなくアポなど、18:00 までに来客者のかたは、 一旦外にでていただいてすぐ左手前のオフィスエリアの表入口からエントランスへお入りください。 ![室町オフィスエントランス2](/assets/blog/authors/uka/access/en2.jpg =600x) こちらから江戸桜通り沿いへ一旦でる ![室町オフィスエントランス3](/assets/blog/authors/uka/access/en3.png =600x) こちらが正面入口 エントランス入って正面のビル受付にて入館手続き後、担当者より指定の階におあがりください。 :::message イベント受付時間以降にいらっしゃるかたも、同じく表入り口からエントランスへ入っていただき、 イベント情報に記載のある担当者の電話番号、もしくは公式 X アカウントへご連絡ください。 担当者が1階へお迎えにあがります。 @ card ::: 東京メトロ半蔵門線『三越前』駅 半蔵門線三越前駅からも直結ですがすこし歩きます・・・ ![半蔵門線からオフィスへの道案内1](/assets/blog/authors/uka/access/han1.png =600x) 日本橋方面改札をでて右にまっすぐ ![半蔵門線からオフィスへの道案内2](/assets/blog/authors/uka/access/han2.png =600x) 小さい階段があるので下って斜め右に ![半蔵門線からオフィスへの道案内3](/assets/blog/authors/uka/access/han3.png =600x) まっすぐ進んで突き当りひだり ![半蔵門線からオフィスへの道案内4](/assets/blog/authors/uka/access/han4.png =600x) ここがコレド室町2です! ![半蔵門線からオフィスへの道案内5](/assets/blog/authors/uka/access/es.png =600x) はいってエスカレーターを上って… ![半蔵門線からオフィスへの道案内6](/assets/blog/authors/uka/access/en1.png =600x) すぐにみえるオフィスエリアになります! :::message イベント受付時間内はオフィスエリアのエントランスに受付が立っています! コレド室町2からオフィスエリアへは入館カードがないと入れないため、受付時間内は来場者っぽいかたを見つけたら扉を開けております。 ::: イベントではなくアポなど、18:00 までに来客者のかたは、 一旦外にでていただいてすぐ左手前のオフィスエリアの表入口からエントランスへお入りください。 ![室町オフィスエントランス2](/assets/blog/authors/uka/access/en2.jpg =600x) こちらから江戸桜通り沿いへ一旦でる ![室町オフィスエントランス3](/assets/blog/authors/uka/access/en3.png =600x) こちらが正面入口 エントランス入って正面のビル受付にて入館手続き後、担当者より指定の階におあがりください。 :::message イベント受付時間以降にいらっしゃるかたも、同じく表入り口からエントランスへ入っていただき、 イベント情報に記載のある担当者の電話番号、もしくは公式 X アカウントへご連絡ください。 担当者が1階へお迎えにあがります。 @ card ::: JR総武快速『新日本橋』駅 新日本橋駅からも直結ですがすこし歩きます・・・ ![新日本橋駅からオフィスへの道案内1](/assets/blog/authors/uka/access/sin1.png =600x) 改札をでて左にまっすぐ三越駅方面へ(新日本橋駅は改札がひとつ!) ![新日本橋駅からオフィスへの道案内2](/assets/blog/authors/uka/access/sin2.png =600x) 三越駅方面へ左に曲がる ![新日本橋駅からオフィスへの道案内3](/assets/blog/authors/uka/access/sin3.png =600x) 銀座線を右手にさらにまっすぐ ![新日本橋駅からオフィスへの道案内4](/assets/blog/authors/uka/access/sin4.png =600x) コレド室町1が左手にでてくるので入って通り抜ける ![新日本橋駅からオフィスへの道案内5](/assets/blog/authors/uka/access/muro.png =600x) 通り抜けた先にコレド室町2の入口があります ![新日本橋駅からオフィスへの道案内6](/assets/blog/authors/uka/access/es.png =600x) はいってエスカレーターを上って… ![室町オフィスエントランス1](/assets/blog/authors/uka/access/en1.png =600x) すぐにみえるオフィスエリアになります! :::message イベント受付時間内はオフィスエリアのエントランスに受付が立っています! コレド室町2からオフィスエリアへは入館カードがないと入れないため、受付時間内は来場者っぽいかたを見つけたら扉を開けております。 ::: イベントではなくアポなど、18:00 までに来客者のかたは、 一旦外にでていただいてすぐ左手前のオフィスエリアの表入口からエントランスへお入りください。 ![室町オフィスエントランス2](/assets/blog/authors/uka/access/en2.jpg =600x) こちらから江戸桜通り沿いへ一旦でる ![室町オフィスエントランス3](/assets/blog/authors/uka/access/en3.png =600x) こちらが正面入口 エントランス入って正面のビル受付にて入館手続き後、担当者より指定の階におあがりください。 :::message イベント受付時間以降にいらっしゃるかたも、同じく表入り口からエントランスへ入っていただき、 イベント情報に記載のある担当者の電話番号、もしくは公式 X アカウントへご連絡ください。 担当者が1階へお迎えにあがります。 @ card ::: JR中央線・京浜東北線・山手線『神田』駅 最後に神田駅からの行き方を紹介しますね! ![神田駅からオフィスへの道案内1](/assets/blog/authors/uka/access/kan1.png =600x) 南口(日本橋方面口)をでてスターバックス沿いを進む ![神田駅からオフィスへの道案内2](/assets/blog/authors/uka/access/kan2.png =600x) 横断歩道を渡って左へまっすぐ ![神田駅からオフィスへの道案内3](/assets/blog/authors/uka/access/kan3.png =600x) 横断歩道を渡って右へまっすぐ ![神田駅からオフィスへの道案内4](/assets/blog/authors/uka/access/kan4.png =600x) まだまだまっすぐ ![神田駅からオフィスへの道案内5](/assets/blog/authors/uka/access/kan5.png =600x) コレド室町3の手前を左へまっすぐ ![神田駅からオフィスへの道案内6](/assets/blog/authors/uka/access/kan6.png =600x) コレド室町2が見えてきますが、そのまままっすぐ通過してください ![神田駅からオフィスへの道案内7](/assets/blog/authors/uka/access/kan7.png =600x) 左手にオフィスエントランスが見えてくるのでこちらになります! ![神田駅からオフィスへの道案内8](/assets/blog/authors/uka/access/en1.png =600x) オフィスエリアのエントランス内に看板をもった受付が立っています(イベント受付時間内) イベントではなくアポなど、18:00 までに来客者のかたは エントランス入って正面のビル受付にて入館手続き後、担当者より指定の階におあがりください。 :::message イベント受付時間以降にいらっしゃるかたも、同じく表入り口からエントランスへ入っていただき、 イベント情報に記載のある担当者の電話番号、もしくは公式 X アカウントへご連絡ください。 担当者が1階へお迎えにあがります。 @ card ::: さいごに 以上、KINTOテクノロジーズ室町オフィスに足を運んでくださる人に 少しでも役に立てれば!な記事でした! ご来場いただきありがとうございます! またのお越しをお待ちしております(^_^)/
アバター