TECH PLAY

セーフィー株式会社

セーフィー株式会社 の技術ブログ

246

こんにちは!第5開発部業務システム第1グループの常世田です。 この記事は セーフィー株式会社 Advent Calendar 2024 の12月20日の記事です! 2024年4月1日、顧客お問い合わせ対応ツールをZendeskからSalesforce Service Cloudへ移行しました。移行前は営業部がSalesforce、カスタマーサービス部(以降CS部)がZendeskと部門ごとに違うツールを利用してきたことにより、データ一元管理がされてない、非効率な業務が多々発生している状態でした。 この記事ではService Cloud導入をどのように実現したかを説明します。 1.導入前の大きな課題 2.実現方法 3.試行錯誤した点 4.取り組んだ感想 1.導入前の大きな課題 顧客対応履歴の分断 営業部はSalesforceを利用して顧客対応をし、CS部ではZendeskを利用して顧客対応をしていました。分断されることで、1顧客に対しての対応履歴がすべて網羅できない状態になり、両部署でどのようなやりとりをしているか、素早くキャッチするのが困難になっていました。 顧客情報が正しくない SalesforceとZendeskはそれぞれ内部で顧客情報を管理しています。Salesforceにすべての情報が連携されておらず、一部の顧客で情報の不足や二重管理になり、正しい情報が一元管理されていないため判定が困難な状態でした。        顧客に関連する情報がZendeskで管理されていない お問い合わせ対応のスタートはカメラのシリアルナンバーを頂いてからスタートします。シリアルナンバーはSalesforceで管理されているため、ツールを横断してSalesforceで検索し状況把握、Zendeskに戻り顧客へ返信する といった非効率な作業が発生していました。        2.実現方法 業務要件の洗い出し Zendeskで実現できていた機能がSalesforceで実現可能か否かの洗い出しを実施。 実現不可の場合は、どうやって実現するか、そもそも運用でカバーできる等も検討し、すべての機能を実現するのではなく、時には業務をシステムに合わせてもらうなど取捨選択をしつつ、一つ一つ丁寧に方針を決めていきました。 流入チャネルの整理 各流入チャネルからのお問い合わせデータ連携先がZendeskに集約されていたため、Zendeskにどういうお問い合わせが集約されているかの洗い出しを行いました。 標準とカスタムする範囲の選別( ※実装の一部分を抜粋 ) 標準機能を用いて対応 メール機能 顧客満足度アンケート チャット機能 Slack連携(Slackワークフローを利用) 電話機能(MiiTel)(※一部カスタマイズ実施) 開発して対応 お問い合わせフォームの連携(DataSpider Cloud) プラン変更/解約申請の連携(DataSpider Cloud) 業務効率化のためのSalesforce内カスタマイズ(関連情報の自動取得/他システム連携) お問い合わせ情報を正しい顧客へ紐づける運用の意識付け 一番のメリットはすべてのお問い合わせが適切な顧客に紐付けられ対応履歴が網羅できる点です。データの紐づきが汚い状態では、導入しても意味がありません。紐付けはなるべくSalesforceで自動的に行いますが、どうしても手動で対応しないといけない部分も発生しました。Zendeskではあまり意識していなかった顧客データ管理の意識付けを行い、正しい情報が正しい顧客へ紐づくようにオペレーションを見直して業務側へ周知を徹底しました。 Zendeskデータ移行 Zendesk内で保管されていたお問い合わせ履歴をすべてSalesforceへ移行しました。Zendeskの仕様上、データを抽出しただけではSalesforceへデータ移行はできず、データ成形方法から検討しつつ、移行作業を実施しました。 3.試行錯誤した点 どのような業務をしているかのキャッチアップ システム導入する前に大切なことは業務がどのような流れで進んでいるかという業務を理解することです。私は入社して早々にService Cloud導入を一任されました。入社研修でCS部がどのような業務をやっているかの座学はあったものの、実際に業務メンバーとの会話ではイメージが沸きずらい状態でした。当時業務PMを担当していた方に不明点あればすぐ確認し、どういう業務をしているかの理解から重点的に行い、周りの方にもサポートを頂きながらプロジェクトを推進していきました。特に業務PMの方にはとても助けられました(笑) Zendesk中心の機能要件 「Zendeskで出来ていたことだからSalesforceでもやってくれ」 という無理難題な要望もありました。運用でカバーするか、システム側で頑張って実装するか、実装した場合スケジュール的に間に合うか等考えた上でどこで着地させるか、という折衷案出しに苦労しました。 データ移行 Zendeskから出力されるデータはJSON形式になっており、Salesforceへ投入するにはCSV形式への変換が必要でした。 JSONデータを扱うことが私自身初めてでしたので移行に必要な項目がどのような構造となっているかから調査を始めました。変換にはCLIコマンドとPythonを用いました。 また、お問い合わせのデータだけではなく、紐づくメールデータ、添付ファイルデータも対象でしたので、3つのデータが正しく出力されるように相当苦労しました。 データ投入のリハーサルは何度もトライしながら、正しいデータでSalesforceへ移行されるよう慎重に対応をしました。またメールデータは通常のSalesforceレコードの容量(2KB)とは異なっていたため、Zendeskデータをすべて移行した際どの程度データ量が増えるかの試算も必要でした。今思い返すとでService Cloud導入プロジェクトで一番パワーを使った作業だったと思っています。 4.取り組んだ感想 今回のプロジェクトでは、要件定義〜開発、リリース、業務調整、運用定着支援まで幅広い領域に携われました。急遽発生する業務要件の変更や追加要望も多々ありましたが、業務側とシステム側がより良い関係を構築することでスムーズに推進できました。どちらかが一方的に要望を伝えるのではなく、お互いの理解があってこそ実現できたプロジェクトだと感じました。「現場を知る」という大切さをこのプロジェクトを経て学べることができたと思っています。 結果、Service Cloudを導入して良かったと思っています。Salesforceに情報が一元管理されることで、顧客対応履歴が瞬時に見れる世界です。また業務効率化という点でもService Cloudの機能が十分発揮し、業務側の力になっていると感じています。 セーフィーのビジネスが拡張していくと同時に新たなカスタマイズ要望もたくさん増えていくと思いますので、業務がスムーズにいくよう引き続き改善、サポートをしていきたいと思っています。 最後まで読んでいただきありがとうございました。
This post is for day 19th of Safie Engineers' Blog! Advent Calendar AWS Kinesis Data Stream and Apache Flink are two of the most popular tools for streaming data processing applications. In today's post, we will explore how those two tools can enhance each other and how Apache Flink compares to AWS counterpart AWS Kinesis Consumer Library (KCL) What is Apache Flink When use Apache Fink over KCL Application Code example DataStreamAPI example TableAPI example Deployment on AWS cloud Conclusion What is Apache Flink In order to understand how Apache Flink can enhance a AWS Kinesis application, firstly, we need to understand its purpose and basic characteristics. Apache Flink is an open-source stream processing framework developed to go hand in hand with data streaming processes, including, but not limited to, the open-source message broker Apache Kafka . Its goal is to be used on unbounded and bounded data streams in real-time and batch processing. One of its strengths is the ability to connect to various data sources and its final destination (downstream data sinks), including data streaming services, like AWS Kinesis Data Stream, filesystem such AWS S3 and directly to Databases, using JBDC Drivers. Fig1. Flink Application data flow Flink was developed considering native cloud deployment, providing a robust and scalable platform for building data-driven applications, offering features such as event time processing, stateful computations, fault tolerance, scalability. When use Apache Fink over KCL While KCL allows record processing checkpoint for each shard iterator for recovery, it does not support state recovery. On the other hand, besides record checkpoint recovery, Flink also allows for complex data structure state recovery. Similar to checkpoint settings, the user can define not only the frequency of state save, but also the backend location. It can be saved locally, on a filesystem, such as S3 or in a key-value store. In case of a failure, Flink restores the state from the latest checkpoint and replays the process until the current point. The number of restart attempts and delay between each attempt is also configurable. This robust state recovery process allows for a better fault tolerance and consistency (exactly-once processing). Another advantage of Flink over KCL is its ability to process records using event-time semantics. KCL does not provide event-time semantics. All the processing logic is based on arrival time. The implementation of event-time processing can be done using a custom logic for out-of-order events buffer, but its implementation can be cumbersome and error prone, besides its overhead on the processing power. Flink natively handles out-of-order records using a watermark strategy, which can be either monotonously increasing (only accept records with higher timestamp than previous) or fixed amount of lateness behind (only accept records within a defined duration behind the previous watermark). This system of event-time semantics allows for accurate window aggregation operations and time-based analysis. It also increases the scalable and resilient processing capability under unstable data flow conditions. The architecture and scalability characteristics of both Fink and KCL differ in many ways. White KCL allows for horizontal scalability, ultimately, the number of workers is limited by the number of active shards. Each KCL instance subscribes to one or more shards, and while multiple KCL can subscribe to one shard, it can lead to a record being processed multiple times. Flink uses a multiple node architecture, allowing for horizontal scalability not bounded by the number of shards. Flink's overall architecture is also more resilient and robust against failure. The process control is based on Task Manager and Job Managers instances. With the Job Manager acting as the central work coordinator handling the Task Manager, who performs the actual work. JobManager is also responsible for detecting and handling processing failures, including establishing new Task Managers nodes and retrieving the previous state and checkpoint. Task Managers can have internal sub-task slots, which with its own purpose and sharing data. Fig2. Flink internal Managers architecture One point where KCL clearly excels is in its simplicity to deploy to production. While a Flink Application has many moving parts that must be adjusted and managed, KCL is an out-of-the-box solution on the AWS environment. Although modem deployment tools, such as Amazon Kinesis Data Analytics for Apache Flink, and solution vendors can help to lower the difficulties of a Fink Application deployment, it is still necessary a high level of understanding of its inner architecture and definitions to really take the full potential of its usage. Application Code example Now, to get a feeling of how the application is written, lets try to make a simple example considering a hypothetical, but realistic, IoT data analysis and processing. One of the first choices to be made when writing an Flink Application is deciding which API to use. Both DataStreamAPI and TableAPI can be used to transform and analyse the data. DataStreamAPI is a lower level abstraction and allows a higher flexibility, while TableAPI is a higher level of abstraction based on SQL language that requires less code to write and thus quicker development cycles. For this example, let's try one example of each API to get a feeling of how each one can be used. Similar to KCL, Flink backbone is written in Java, but there is a wrapper for Python available. In this example, we will use its native Java language to show a few of its characteristics. So, now to the code: In order to connect to a AWS Kinesis Data Stream, we will first need to include its relative connector in our dependencies. The easiest way to do it is to use a Java build tool such as Maven or Gradle maven <dependency> <groupId> org.apache.flink </groupId> <artifactId> flink-connector-aws-kinesis-stream </artifactId> <version> 5.0.0-1.20 </version> </dependency> gradle implementation 'org.apache.flink:flink-connector-aws-kinesis-streams:5.0.0-1.20' While the datastream can be used unstructured and undefined, I will help a lot in the long run if we define the data schema that we expect to be receiving. For this particular case, let's assume the data is for a device that will be sending periodically a packet of data in bytes and a value related to its status. Keep in mind that Flink has a native AWS Glue Schema decoder that can also be used for bigger projects where the schema must be shared across several services. public class DeviceRecord implements Serializable{ public String device_id; public long timestamp; public double value; public String data; public DeviceRecord() { } public DeviceRecord(String device_id, long timestamp, double value, String data) { this .device_id = device_id; this .timestamp = timestamp; this .value = value; this .data = data; } public String getDeviceId() { return device_id; } public long getTimestamp() { return timestamp; } public double getValue() { return value; } public byte [] getData() { return data != null ? data.getBytes() : null ; } @Override public String toString() { return "DeviceRecord{" + "device_id='" + device_id + ' \' ' + ", timestamp=" + timestamp + ", value=" + value + ", data=" + data + '}' ; } } The next step is to define how to deserialize the data received from the stream. We can get that byte array and transform it into an DeviceRecord instance. public class DeviceRecordDeserializationSchema implements DeserializationSchema<DeviceRecord> { private static final long serialVersionUID = 1L ; @Override public DeviceRecord deserialize( byte [] message) throws IOException { String line = new String(message, StandardCharsets.UTF_8); ObjectMapper objectMapper = new ObjectMapper(); DeviceRecord deviceRecord = objectMapper.readValue(line, DeviceRecord. class ); return deviceRecord; } @Override public boolean isEndOfStream(DeviceRecord nextElement) { return false ; } @Override public TypeInformation<DeviceRecord> getProducedType() { return TypeInformation.of(DeviceRecord. class ); } } DataStreamAPI example This is the actual implementation code of the processing job that connects with the Kinesis DataStream. For now no actual processing is being done. We will implement the sink connector latter public class BasicStreamingJob { public static void main(String[] args) throws Exception { Configuration sourceConfig = new Configuration(); sourceConfig.set(KinesisSourceConfigOptions.STREAM_INITIAL_POSITION, KinesisSourceConfigOptions.InitialPosition.LATEST); // Create a new KinesisStreamsSource to read from specified Kinesis Stream. KinesisStreamsSource<DeviceRecord> kdsSource = KinesisStreamsSource.<DeviceRecord>builder() .setStreamArn( "your-stream-arn" ) .setSourceConfig(sourceConfig) .setDeserializationSchema( new DeviceRecordDeserializationSchema()) // The DeviceRecord deserializer .setKinesisShardAssigner(ShardAssignerFactory.uniformShardAssigner()) // This is optional, by default uniformShardAssigner will be used. .build(); WatermarkStrategy<DeviceRecord> watermarkStrategy = WatermarkStrategy.<DeviceRecord>forBoundedOutOfOrderness(Duration.ofSeconds( 10 )) .withTimestampAssigner((event, timestamp) -> event.getTimestamp()); final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(); env.enableCheckpointing( 5000 ); // Define the data processing pipeline. We will create one job for each device_id KeyedStream<DeviceRecord, String> stream = env.fromSource(kdsSource, watermarkStrategy, "Kinesis Source" ) .returns(TypeInformation.of(DeviceRecord. class )) .keyBy(value -> value.getDeviceId()); // Print the records to the console stream.print(); // Execute the Flink job env.execute( "Order Records by Partition Key" ); } } In this example, we will retrieve the field “data” from the Device object and insert it in as AWS S3 bucket. The first step it to decide how to retrieve this data. This can be done with an encoder class that writes the data into an OutputStream. public class DeviceRecordEncoder implements Encoder<DeviceRecord> { @Override public void encode(DeviceRecord element, OutputStream stream) throws IOException { stream.write(element.getData()); } } After that we will need to decide where to put our objects. For this case, we will create a daily bucket, combined with the device ID. public class DeviceRecordBucketAssigner implements BucketAssigner<DeviceRecord, String> { private static final long serialVersionUID = 1L ; private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "yyyy-MM-dd" ); @Override public String getBucketId(DeviceRecord element, Context context) { LocalDate date = Instant.ofEpochSecond(element.getTimestamp()).atZone(ZoneId.of( "Z" )).toLocalDate(); return date.format(formatter) + "/" + element.getDeviceId(); } @Override public SimpleVersionedSerializer<String> getSerializer() { return SimpleVersionedStringSerializer.INSTANCE; } } Finally, now we can combine all these elements and create our processing job to insert the data into an S3 bucket, by adding the following code to the main function. // Define the S3 sink FileSink<DeviceRecord> s3Sink = FileSink .forRowFormat( new Path( "s3://testbucket/" ), new DeviceRecordEncoder()) // Use the custom encoder .withBucketAssigner( new DeviceRecordBucketAssigner()) // Use the custom bucket assigner .withRollingPolicy(DefaultRollingPolicy.builder() // Roll the file every 5 seconds .withRolloverInterval(Duration.ofSeconds( 5 )) .withInactivityInterval(Duration.ofMinutes( 1 )) .withMaxPartSize(MemorySize.ofMebiBytes( 16 )) .build()) .build(); // Add the S3 sink to the pipeline stream.sinkTo(s3Sink); With this piece of code, we can insert the "data" field from the Kinesis record into the S3 according to the rolling policy as we receive it. TableAPI example For the example with TableAPI, let’s consider a job that inserts the streaming data directly into a RDS Database. In this example, we will insert the Device field “value” into the table as we receive it. final StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env); // Define the Kinesis source table tableEnv.executeSql( "CREATE TABLE kinesis_table (" + " device_id character varying(32)," + " `timestamp` NUMERIC," + " `value` NUMERIC" + ") " + "PARTITIONED BY (device_id) WITH (" + " 'connector' = 'kinesis'," + " 'stream.arn' = 'your-stream-arn'," + " 'format' = 'json'" + ")" ); // Define the PostgreSQL sink table tableEnv.executeSql( "CREATE TABLE postgres_table (" + " device_id VARCHAR(32)," + " `timestamp` NUMERIC," + " `value` NUMERIC" + ") WITH (" + " 'connector' = 'jdbc'," + " 'url' = 'jdbc:postgresql://db-host:port/dbname'," + " 'table-name' = 'yourtablename'" + ")" ); // Insert data from Kinesis source table to PostgreSQL sink table tableEnv.executeSql( "INSERT INTO postgres_table " + "SELECT device_id, `timestamp`, `value` FROM kinesis_table" ); The above TableAPI example shows how it trades granularity and control for simplicity and quick deployment. It is ideal to work with highly structured data; or for cases when it requires JOIN between several data sources. For either cases, both APIs can be mixed at will for any Flink Application. So choose the API you will be using depending on your necessity. Deployment on AWS cloud With your Flink Application ready, now you can deploy it and start analyzing the stream data. Strictly speaking, the deployment can be done even locally, but to really reap the benefits of scaling and parallel processing, deployment on the cloud is recommended. Since we will already be using AWS Kinesis Datastream, let's consider the options for deployment on AWS Cloud. The deployment can be done on several different services, each one with its own benefits and trade-offs. Amazon Kinesis Data Analytics for Apache Flink (previously called Amazon Kinesis Data Analytics) EMR (Elastic MapReduce) AWS Fargate with Kubernetes (EKS) ECS AWS Lambda For architectures based on nodes, such as EKS, another choice that must be made is to decide if the application will be run in Session mode or Application Mode. In Session mode, the resources are shared between the nodes inside the single cluster, lowering the overhead for the overall service, but miss the isolation. This mode is best suited for quick or small jobs. Some special considerations must be taken into account when defining the configuration for a Flink application on the cloud. A critical point that has a direct impact on the cost of operation is regarding resource allocation. The number of tasks should be decided considering the parallelism level that the Task Manager will use to process the job, while the instance size should be decided considering the expected load. While Flink allows for those parameters to be set in its configuration file flink.conf, most services, such as EMR and Amazon Kinesis Data Analytics for Apache Flink allow to integrate its autoscaling to AWS native metrics and setup. Conclusion In this post we showed how Apache Flink can be used to analyze and process data coming from a AWS Kinesis Data Stream. When compared to AWS native solution, KCL, Flink has a few advantages, such as better flexibility and the ability to create save states. On the flip side, it also increases the complexity of the system, and consequently, the need of inhouse skills. The choice of which stream data analysis framework to use should be taken considering your specific necessities. To have Apache Flink as one of the available tools will surely increase your possibilities to take the data analysis and transformation process even further.
こんばんは。データ分析基盤グループ所属の大室です。 この記事は Safie Engineers' Blog! Advent Calendar 18日目の記事です! はじめに 登壇内容 セッション概要 当社のご紹介 プロジェクト背景 プロジェクト始動 CData Syncの採用ポイント 今後の展望 余談 会場風景 We are hiring! はじめに 2024年9月12日、 Snowflake World Tour Tokyo 2024 にて「SnowflakeとCData Syncを採用したSafieのデータ分析基盤構築におけるポイント」と題したセッションに登壇しました。 本セッションは事前登録時点で満席の申し込みを頂き、当日も多くの方にご参加頂きました! この記事では当日の登壇資料をベースにセッション内容をご紹介します。 なお、本セッションはCData社のスポンサーセッションにご招待頂く形で実現しました。 登壇の機会を頂いたCData社とSnowflake社の皆様にはこの場をお借りして感謝申し上げます。 会場は昨年と同じく、 ANAインターコンチネンタル東京 でした。 登壇させて頂いたセッション会場はこんな感じです。 CData社の杉本さん(左)と共同で登壇しました。 登壇中はCData社の疋田社長に撮影頂きました。ありがとうございました! 登壇内容 セッション概要 まず最初に、セッションの概要についてお伝えします。 当社では複数SaaS導入によりデータのサイロ化が加速し、財務会計/管理会計におけるデータ分析上の課題が発生しました。 この課題を解決する上でデータ分析基盤が重要な役割を担うことになったのですが、その背景やポイントについてご説明したいと思います。 当社のご紹介 本題に入る前に、当社について紹介させて下さい。 当社はクラウドカメラの映像プラットフォームを提供する会社で、会社名もサービス名もセーフィー(Safie)と言います。 当社のサービスに対応するカメラをご利用頂くと、リアルタイムで映像がクラウドにアップロードされ、Webやモバイルの専用アプリから確認できる、というのが基本機能になっています。 基本機能以外にも、YouTube Liveと連携する機能や、AIで人を検知する機能など、オプション機能も充実しています。 現在、当社のサービスに対応するカメラは全体で約26万台ご契約頂いており、ユーザー様の用途は多様化しています。 やはり防犯・監視用途で利用されることが多いのですが、防犯だけではなく、建設や工事の現場における異常検知や現場監督のリモート化など、人材不足解消を目的とした用途でも広く利用されています。 当社のサービスはオプション含め、基本的にサブスクリプション型で提供しているため、売上全体に占めるリカーリング売上の割合が大きいです。 また、当社はカメラ本体の販売や設置工事も行っておりますので、一般的なSaaS企業と比較するとスポット売上の割合が大きいのが特徴的だと思います。 プロジェクト背景 当社では元々、サブスクリプションの契約管理にSalesforceを使用していました。 しかし、Salesforceはいわゆるワンショットの取引をベースに設計されているサービスなので、サブスクリプションの契約、更新管理、月単位での請求のレコード自動生成等はアドオンを開発しなければならず、オペレーションコストや開発コストが非常に多くかかっていました。 そこで、ビジネスオペレーションの最適化、オペレーションコストの削減や開発コストの抑制を目的として、サブスクリプションに特化した契約管理サービスのZuoraを導入することになりました。 しかし、Zuora導入に伴い別の課題が浮上しました。端的に申し上げると、データのサイロ化です。 Zuora導入後もSalesforceはSFAとして継続利用するため、商談や商流、各種マスタデータはそのままSalesforceで維持されます。 一方、Zuora導入後はSalesforce上のサブスクリプション契約関連オブジェクトが更新されなくなるため、元々Salesforceのレポート機能で完結していた財務会計/管理会計に関連するレポートも更新されなくなるという課題が発生しました。 プロジェクト始動 前述の課題が顕在化したのはZuora本番稼働開始の数ヶ月前。上場企業としてタイムリーに会計業務を遂行できなくなることは、経営上の大きなリスクでした。 この課題を解決するためには、サイロ化している各種データをどこかで結合し、財務会計/管理会計レポートを再構築する必要がありました。そこで声を掛けられたのが、私が管轄しているデータ分析基盤グループです。 データ分析基盤グループでは元々、 Safieプラットフォーム(サービスのバックエンドDB)のデータをSnowflake(DWH)に連携し、Tableau(BIツール)で可視化する仕組みを構築していました。 しかし、SalesforceやZuoraをSnowflakeに連携する仕組みは構築していませんでした。当初はスクラッチ開発で仕組みの構築を検討しましたが、データエンジニアのリソースが不足している事情もあり断念。 短期間で確実に開発を終えられる方法を模索した結果、Salesforceのデータ連携用に CData Sync 、Zuoraのデータ連携用に Zuora Secure Data Share for Snowflake (Zuora社からSnowflakeのデータシェアリング機能でデータを共有頂くサービス)を導入しました。 各ツールの検証には1ヶ月程度掛かりましたが、CData社やZuora社のサポート、社内のSalesforceを管轄するチームと密に連携し、何とか正式導入に漕ぎ着けることが出来ました。 ちなみに、CData SyncはAWSのEC2インスタンスでホスティングして運用しています。 そして、既存の財務会計/管理会計レポートのロジックを、各種データを掛け合わせた形で再構築し、Tableauで可視化。会計業務の継続に大きく貢献することができました。 CData Syncの採用ポイント CData Syncの導入はすんなり決まったわけではなく、OSSのツールをホスティングする方法や、各種データ連携SaaSと比較検討していました。 CData SyncはGUIベースで容易にジョブの実装が可能であり、履歴化処理やメタデータの自動検出によるスキーマ同期機能・dbt Cloudとの連携機能を有しているため、少ない開発リソースでも短期間で実装可能なイメージを持つことができました。 また、中長期的にはSalesforce以外のSaaSとの連携にも活用でき、価格帯も予算にフィットしたことから、当社のデータ分析基盤にはCData Syncが適していると判断しました。 今後の展望 今回のプロジェクトは短納期かつデータエンジニアの工数が不足していた事情から、やむを得ずSalesforceやZuora由来のデータにはデータモデリングを適用せず、Tableau側でデータの結合や変換処理の大部分を実装しました。 そのため、開発者目線だと運用・保守の負荷が高く、利用者目線だとTableauを経由しないと財務会計/管理会計に関するデータを閲覧できないという課題が生じています。 また、コスト抑制の観点から、Tableauのライセンスは定期利用の見込みがある一部の利用者にしか提供できないという社内事情も相まって、データの民主化のボトルネックになっています。 現在はこのような状態ですが、 今後はSalesforceやZuora由来のデータにもデータモデリングを適用し、Tableau側の処理依存度を低下させ、データ分析基盤全体の拡張性や保守性を向上し、データの民主化を推進できる土壌を整えていきたいと考えています。 余談 イベント登壇前後で会場を徘徊しておりましたので、当日の写真を数点共有します。会場の雰囲気が少しでも伝われば幸いです。 ちなみに、Snowflake社公式で Snowflake World Tour Tokyo 2024のハイライト動画 が公開されていますので、こちらもぜひチェックしてみて下さい。 会場風景 Keynoteセッションは満席。Snowflake社のスリダール・ラマスワミCEOが登壇していました。 Snowflake界のアイドル(?)くま太郎。可愛さが爆発していました。 CData社のブースには、 SafieのAIソリューション「AI-App人数カウント」のデータ連携にCData Syncが対応した件 のチラシを置いて頂きました。ありがとうございます! 来訪者には、Snowflakeに連携したいサービスにシールを貼ってもらっていたようです。 Safieにもシールが貼ってありますね! Snowflakeの資格を持っていると素敵なグッズが貰えます。 今年はイケてる缶バッジとTシャツでした。家宝にします。 We are hiring! 最後になりますが、データ分析基盤グループでは一緒に働いてくれる仲間を大募集中です! セーフィーのデバイスやアプリケーションからは1日あたり約80億件のログが発生し、データ量は線形的に増加しています。 このような超ビッグデータを効率的に処理し、ビジネスの意思決定に繋げるための仕組みを一緒に作ってみませんか? ご興味のある方は セーフィー採用サイト を覗いてみて下さい。 皆様のご応募、心よりお待ちしております!
この記事は Safie Engineers' Blog! Advent Calendar 17日目の記事です はじめに こんにちは、第1開発部でサーバーサイドエンジニアをしている伊東公平です。今回は、昨年の11月から今日まで1年以上に渡って、スクラムマスターとしてチームの改善のために試行錯誤してきた内容についてお伝えします。 はじめに 課題 取り組んだこと スクラムの基盤づくり チームの強化 適切なツールへの移行 プロダクトバックログ スプリントバックログ 最後に 課題 私がスクラムマスターとして就任した際にはすでにチームでスクラム開発を採用していました。しかし、その中での課題はまだ多くありました。その中でも私個人としては、以下のようなものに改善するメリットがあると感じていました。 課題①: スプリントゴールが不明瞭で、スプリントの途中で現時点の進捗を把握する手段が少ない。そのため、チームとしての目的意識が保ちづらい 課題②: タスクの背景理解ができていないままタスクをとり、実装することで仕様とのズレが生じ、手戻りが発生しやすくなる 課題③: 使用しているツールがバラバラで、ツール間の連携がうまく取れていないため、ツールの管理や移行に時間がかかる 取り組んだこと 上記のような課題を感じていた中で、これに対処するために大きな取り組みとして以下のようなことを1年に渡り行ってきました。この3つの取り組みに対し、それぞれの目的と効果について以降では説明していきたいと思います。 スクラムの基盤づくり スクラムマスターとして最初に取り組んだのは、チーム内でのスクラムに対する認識やルールを整えることでした。これは先に挙げた課題①を解決するために行いました。 スクラムイベントの開催日時を見直し固定化 当初、スクラムイベントが開催される日時が非効率な点がいくつかありました。 例えば、スプリントの切り替わりで半日時間が空いてしまうため、スプリント間の連続性がなくなっていたというようなものです。 そのため、以下のようにスプリントの振り返りとプランニング間での連続性を持たせるようにスプリントのスケジュールを見直しました。特にスプリントの振り返りとプランニングは振り返りを行ったことを即座にプランニングで反映できるようにすることを意識しました。 定量的に現在の進捗を計測 チーム内でスプリントの途中でも現在の進捗を定量的に確認できるように、以下のようなバーンダウンチャートを作成しました。これにより、進捗が遅れている場合は朝会でそれを確認できるようになり、振り返りの際にその原因をチームで会話できる体制を整えました。 上記の2点の取り組みにより、スプリントの区切りがはっきりし、進捗も明らかになり、いつまでにスプリントゴールを達成するといった意識がより強くなったと考えています。 チームの強化 次にチーム内で取り組んだのは 強いチーム 作りです。私の中の定義としては、「一人一人が自律し、スプリントでのタスクを消化できるような状態」を目指すために行いました。 これは課題②の解決にもなると考え行いました。 具体的には、プランニングの際に以下のようなユーザーストーリーを作成するようにしました。 ユーザーストーリーとは、エンドユーザー目線から必要な機能をタスクに分解していく手法です。青枠がサービスの機能でそれを黄色枠部分のユーザーストーリー、さらにその下のタスクまで分解する作業をチームメンバー全員で行うようにしました。 ユーザーストーリーを作成することで、タスクの背景を重点的に説明する機会が増え、チーム全員が以前より背景を理解した上でタスクの消化を行うことができるようになりました。また、タスクを作成する際の属人化を避けられるようになり、チームメンバーの誰でもタスクを作成できるという意識づけもできたと考えています。 適切なツールへの移行 最後に、スクラムイベントを行うにあたり、私のチームで使用しているツールを使用方法や現状のチームに合わせていくつか移行してきましたので、その紹介をしたいと思います。 これは、課題③でもあったように、複数ツールを使用することにより、ツール間の連携が取りづらく、タスク管理が二重になってしまっていたり、うまくそのツールを有効活用できていなかったため、この負を解決するために移行しました。 以下に改善前の使用ツールと改善後の使用ツールの一覧を載せます。 種類 改善前 改善後 プロダクトバックログ Miro GitHub Projects スプリントバックログ Backlog GitHub Projects 振り返り Jamboard Miro 上記の移行により、最も効果が現れたのは、プロダクトバックログとスプリントバックログ をGitHub Projectsへ移行したことでした。以下のように、同じツールを使用することで二重管理から解放され、プルリクエストとの紐付け等も行いやすくなりました。 プロダクトバックログ スプリントバックログ 最後に 今回のブログでは、この一年間に渡ってスクラムマスターとして自分のチームをどのように変革してきたのかを紹介しました。ここでは紹介しなかった変革も沢山ありますし、失敗したものもいくつもありますが、全て挑戦して良かったと思っています。 また、今回紹介した内容が全てのチームに当てはまるわけではないので、参考にされる場合も「そんな考え方があるのかと」と思うぐらいで軽く考え、自チームにあった方法に改善して活用していただけると嬉しいです。
はじめに   この記事は Safie Engineers' Blog! Advent Calendar 16日目の記事です  セーフィーのサーバーサイドエンジニアの三村です。  セーフィーのサーバーサイドでは箇所によって色々な言語(Python, Go, Java…)が用いられていますが、コードベースの大部分はPythonを用いています。本記事では、そんな普段セーフィーのエンジニアが用いているPython( *1 )のガベージコレクションの仕組みについて調べたので、まとめてみます。 はじめに 概要の説明 参照数によるガベージコレクションの実装 概要 intのオブジェクトでの例 世代別の循環参照検知のガベージコレクションの実装 世代とは オブジェクトのリストの作成 オブジェクトのリストの走査 到達不能のオブジェクトの探索 まとめ 概要の説明  多くのプログラミング言語には、ユーザーが明示的に行なわずとも自動で不要なデータをメモリから解放するガベージコレクションの仕組みがあります。こちらは各言語ごとに仕組みや詳細な実装は異なっていますが、Pythonの場合には2種類の自動のメモリ解放の仕組みがあります。  一つ目は、Pythonの全てのオブジェクトに被参照数のカウントを持つようにして、それが0になったらメモリ解放をするという単純なものです。  もう一つは、上記の手法では対応できない循環参照の検知のための仕組みです。全てのオブジェクトを「世代」に分けてトラックしておき、それらの中で使われていないものを探索し開放する手法のものです。 参照数によるガベージコレクションの実装 概要  Pythonのオブジェクト(Cのコードでは PyObject と呼ばれる)は全て、被参照数をデータとして持っています。こちらのカウンターが0になるとメモリ解放がされます。こちらの、PyObjectの被参照数を減らして数が0になれば削除をしている実装を見てみます。 static inline Py_ALWAYS_INLINE void Py_DECREF (PyObject *op) { // Non-limited C API and limited C API for Python 3.9 and older access // directly PyObject.ob_refcnt. if ( _Py_IsImmortal (op)) { return ; } _Py_DECREF_STAT_INC (); if (--op->ob_refcnt == 0 ) { _Py_Dealloc (op); } } cpython/Include/object.h at v3.12.7 · python/cpython · GitHub  引数で受け取ったPyObjectの被参照数を表す ob_refcount をデクリメントして、値が0になれば _PyDealloc というメモリ解放の関数を呼び出しています。 呼び出される _PyDealloc の関数は、以下の実装になっています。 // デバッグモードの時等の挙動が長いので、それらを省いて簡略化 void _Py_Dealloc (PyObject *op) { PyTypeObject *type = Py_TYPE (op); destructor dealloc = type->tp_dealloc; (*dealloc)(op); } cpython/Objects/object.c at v3.12.7 · python/cpython · GitHub  削除対象のオブジェクトの型( Py_TYPE )がそれぞれ持つ tp_dealloc という関数を呼び出してメモリ解放を行なっています。このように、Pythonのオブジェクトの型ごとにメモリ解放の実装は異なっています。 intのオブジェクトでの例  いくつもあるPythonの型の中で、一例としてint型のオブジェクトの参照数によるメモリ解放の例を見てみます。( *2 ) long_dealloc (PyObject *self) { /* This should never get called, but we also don't want to SEGV if * we accidentally decref small Ints out of existence. Instead, * since small Ints are immortal, re-set the reference count. */ PyLongObject *pylong = (PyLongObject*)self; if (pylong && _PyLong_IsCompact (pylong)) { stwodigits ival = medium_value (pylong); if ( IS_SMALL_INT (ival)) { PyLongObject *small_pylong = (PyLongObject *) get_small_int ((sdigit)ival); if (pylong == small_pylong) { _Py_SetImmortal (self); return ; } } } Py_TYPE (self)-> tp_free (self); } cpython/Objects/longobject.c at v3.12.7 · python/cpython · GitHub  条件分岐を経て、最後の部分でメモリ解放( tp_free の中で実際にはされています)がされています。  余談ですが、上で引用したコードの中ではこのintの値が特定の基準よりも小さいかどうかを判定( IS_SMALL_INT ) *3 して、小さければ _Py_SetImmortal をしてガベージコレクションにかけられないようにしています。こちらはPython3.12以降には、使用頻度が高いことが見込まれる小さい値のintのオブジェクトを削除しないようにした、パフォーマンス向上目的の変更です。こちらの仕様の導入には色々議論があったようで、気になった方は こちら のPRを見てみてください。 世代別の循環参照検知のガベージコレクションの実装  上記のようなシンプルな参照数によるガベージコレクションがあれば、それで足りるのではないかと思うかもしれませんが、実際はそれでは済みません。Pythonのオブジェクト同士の循環参照のケースが、上のやり方だとカバーできないためです。このケースを避けるため、Pythonにはもう一つ世代別のガベージコレクションの仕組みが存在します。 世代とは  (プログラミングの実行時の一般論として)ほとんどのオブジェクトは作成直後に使用されなくなる傾向があり、作成直後のオブジェクトのリストのほうが作成から時間の経ったオブジェクトよりも、ガベージコレクションの対象となる確率が高くなります。  そのため、循環参照の検知のような重めの処理では、「世代」の若い(作られて間もない)オブジェクトのチェックを頻繁に行い、「世代の古い」(作られて時間の経っている)オブジェクトのチェックはより稀に行うようにして、効率化を図っています。  この考え方は、Python独自のものではなくそのほかのプログラミング言語(Java等)でも一般的に用いられています。 オブジェクトのリストの作成  Pythonのこちらの方式のガベージコレクションでは、世代ごとに循環参照となり得るすべてのオブジェクト(=他のPyObjectへの参照を持っているPyObject)のリストを持ちます。その後、そのリストの内部を走査し、特定の世代内部のみで循環参照をしているものを見つけて、メモリ解放をします。  ここではまず、特定の世代のPyObjectを双方向リストとして保持する実装を見てみます。こちらの双方向リストには、オブジェクトの作成時に追加されます。以下でtupleのオブジェクトの生成時の例を挙げます。 PyObject * PyTuple_New (Py_ssize_t size) { PyTupleObject *op; if (size == 0 ) { return tuple_get_empty (); } op = tuple_alloc (size); if (op == NULL ) { return NULL ; } for (Py_ssize_t i = 0 ; i < size; i++) { op->ob_item[i] = NULL ; } _PyObject_GC_TRACK (op); // 筆者注: ここで作成したオブジェクトを、トラックできるようにしている return (PyObject *) op; } cpython/Objects/tupleobject.c at v3.12.7 · python/cpython · GitHub  引用したコードの最後の方で _PyObject_GC_TRACK 関数を呼び出して、世代別ガベージコレクションで追跡できるよう、作成したtupleのオブジェクトを双方向リスト(各要素が、自身の前と次へのリンクを持っているリスト)に追加しています。  こちらの _PyObject_GC_TRACK の処理の実装を見てみます。 static inline void _PyObject_GC_TRACK ( // The preprocessor removes _PyObject_ASSERT_FROM() calls if NDEBUG is defined #ifndef NDEBUG const char *filename, int lineno, #endif PyObject *op) { _PyObject_ASSERT_FROM (op, ! _PyObject_GC_IS_TRACKED (op), "object already tracked by the garbage collector" , filename, lineno, __func__ ); PyGC_Head *gc = _Py_AS_GC (op); _PyObject_ASSERT_FROM (op, (gc->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0 , "object is in generation which is garbage collected" , filename, lineno, __func__ ); PyInterpreterState *interp = _PyInterpreterState_GET (); PyGC_Head *generation0 = interp->gc.generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT (last, gc); // 筆者注: この辺からリストへの追加を行なっている _PyGCHead_SET_PREV (gc, last); _PyGCHead_SET_NEXT (gc, generation0); generation0->_gc_prev = ( uintptr_t )gc; } cpython/Include/internal/pycore_object.h at v3.12.7 · python/cpython · GitHub  こちらのコード内の gc というものが、引数で受け取ったPyObjectをガベージコレクションで追跡できるような型にキャストしたものです。こちらを、現在のラインタイムにおける最も若い世代のPyObjectの双方向リスト( generation0 )の最後尾に追加しています。 オブジェクトのリストの走査  上で特定の世代のPyObjectのリストに要素を追加する実装例を見たので、次にそのリスト内を走査する実装を見て見ます。こちらは以下の gc_collect_main 関数で実行されています。 /* This is the main function. Read this to understand how the * collection process works. */ static Py_ssize_t gc_collect_main (PyThreadState *tstate, int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail) { int i; Py_ssize_t m = 0 ; /* # objects collected */ Py_ssize_t n = 0 ; /* # unreachable objects that couldn't be collected */ PyGC_Head *young; /* the generation we are examining */ PyGC_Head *old; /* next older generation */ PyGC_Head unreachable; /* non-problematic unreachable trash */ PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ PyGC_Head *gc; _PyTime_t t1 = 0 ; /* initialize to prevent a compiler warning */ GCState *gcstate = &tstate->interp->gc; // 略 // 筆者注: 1. 若い世代のマージ /* merge younger generations with one we are currently collecting */ for (i = 0 ; i < generation; i++) { gc_list_merge ( GEN_HEAD (gcstate, i), GEN_HEAD (gcstate, generation)); } /* handy references */ young = GEN_HEAD (gcstate, generation); if (generation < NUM_GENERATIONS- 1 ) old = GEN_HEAD (gcstate, generation+ 1 ); else old = young; validate_list (old, collecting_clear_unreachable_clear); // 筆者注: 2. 到達不能のオブジェクトの探索 deduce_unreachable (young, &unreachable); untrack_tuples (young); // 筆者注: 3. 到達可能のオブジェクトを古い世代に移動 /* Move reachable objects to next generation. */ if (young != old) { if (generation == NUM_GENERATIONS - 2 ) { gcstate->long_lived_pending += gc_list_size (young); } gc_list_merge (young, old); } else { /* We only un-track dicts in full collections, to avoid quadratic dict build-up. See issue #14775. */ untrack_dicts (young); gcstate->long_lived_pending = 0 ; gcstate->long_lived_total = gc_list_size (young); } // 略 // 筆者注: 4. 到達不能のオブジェクトの削除 /* Call tp_finalize on objects which have one. */ finalize_garbage (tstate, &unreachable); /* Handle any objects that may have resurrected after the call * to 'finalize_garbage' and continue the collection with the * objects that are still unreachable */ PyGC_Head final_unreachable; handle_resurrected_objects (&unreachable, &final_unreachable, old); /* Call tp_clear on objects in the final_unreachable set. This will cause * the reference cycles to be broken. It may also cause some objects * in finalizers to be freed. */ m += gc_list_size (&final_unreachable); delete_garbage (tstate, gcstate, &final_unreachable, old); // 略 } cpython/Modules/gcmodule.c at v3.12.7 · python/cpython · GitHub  上記で引用したコード( *4 )では、引数 generation で受け取った世代のリストのコレクションを実行します。以下のような手順でオブジェクトのリストを走査しています。 1.若い世代のマージ  本関数でコレクションをする対象の世代のリストに、それよりも若い世代のコレクション対象リストをマージします( gc_list_merge 関数)。  例として、第3世代のコレクションをしている場合は、第3世代のリストに第1世代と第2世代のリストをマージして、このリストをガベージコレクションのチェックの対象とします。こうすることで、若い世代のリストほど、ガベージコレクションの走査の対象となる頻度が高くなります。 2.到達不能のオブジェクトの探索   1. 若い世代のマージ で作成したリストの中から、到達不能なオブジェクトを探し出します( deduce_unreachable 関数)。この関数の中では、循環参照を検知(後述)して、参照はあるものの実際は到達不能なオブジェクト(=参照数カウントのガベージコレクションでは検知できないが、実際は削除するべきオブジェクト)を探します。 3.到達可能のオブジェクトを古い世代に移動   2. 到達不能のオブジェクトの探索 でのチェックで引っ掛からなかった、コレクションの対象とすべきでないオブジェクトを、一つ古い世代に移します。こちらは先述した通り、作成されてから時間が経ったオブジェクトほど削除対象となる確率が下がるためです。 4.到達不能のオブジェクトの削除   2. 到達不能のオブジェクトの探索 で削除が必要と判明したオブジェクトを、実際に削除します。 到達不能のオブジェクトの探索  上で、PyObjectのリストから到達不能なものを探索して削除する実装を軽くみてみました。次に、具体的にどのようなロジックで到達不能なオブジェクトを探しているのかを見てみます。上で到達不能なオブジェクトを探索するのに用いていた、 deduce_unreachable の実装を引用します。 // 筆者注: コードに元々あったコメントを削除し、筆者のコメントを追加 static inline void deduce_unreachable (PyGC_Head *base, PyGC_Head *unreachable) { validate_list (base, collecting_clear_unreachable_clear); update_refs (base); // 1. GC用のカウンターをセット subtract_refs (base); // 2. 他のオブジェクトからの参照を探して、その分カウンターを減らす gc_list_init (unreachable); move_unreachable (base, unreachable); // 3. カウンターの値を元に、削除対象のオブジェクトを探す validate_list (base, collecting_clear_unreachable_clear); validate_list (unreachable, collecting_set_unreachable_set); } cpython/Modules/gcmodule.c at v3.12.7 · python/cpython · GitHub  まず、 1. GC用のカウンターをセット の部分で、検査対象の世代の全オブジェクトのリスト(実装上は containers と呼ばれる)を走査し、参照数によるガベージコレクションに用いられている obj_refcnt とは別に本処理用に gc_refs というカウンターを設定します。新しく設定した後者のカウンターには、デフォルト値として前者と同じ値を入れます。  以下に、簡単な図解を用意します。ここでは、オブジェクト A から E までがあり、それら全てに gc_refs の値を参照数である obj_refcnt の値を設定しています。この例だと、 A , B , C はこの世代の外から参照されており削除対象とはなりませんが、 D と E はお互いの循環参照のみによって参照されていて、もう利用されておらずガベージコレクションで削除されるべきオブジェクトになります。 各オブジェクトにおいて、obj_refcntの数をgc_refsにコピーする  次に、上で引用したコードの 2. 他のオブジェクトからの参照を探して、その分カウンターを減らす の部分で、GC対象のオブジェクトのリストでイテレーションを回します。その中で、それぞれのオブジェクトが持つ他のオブジェクトへの参照を探し出して、参照先のオブジェクトの gc_refs のカウンターを1ずつ減らします。  例えばオブジェクトがdict型であれば、自身の辞書のkeyとvalueの両方のオブジェクトの gc_refs をデクリメントします。( *5 )  以下の例だと、 A は B への参照を持つため B のgc_refsを減らし、同様に B は C への参照を持つためCのgc_refsを減らします。 D と E はお互いを参照しているので、お互いのgc_refsを減らします。 各オブジェクトの参照を辿って、参照先のオブジェクトのgc_refsを1ずつ減らしていく  その後、上記引用のコードの 3. カウンターの値を元に、削除対象のオブジェクトを探す の箇所で、 gc_refs の値を元に循環参照でのみ参照されているオブジェクト(=参照数が0ではないがもう利用されていないオブジェクト)を探します。  まず、 gc_refs が0であるオブジェクトと1以上であるオブジェクトに分離します。オブジェクトに参照がある場合はそのまま containers に置かれ、なければ unreachable というリストに移されます。   gc_refs が0である場合、この世代の外側から参照されていない可能性があります。こちらが、ガベージコレクションの対象となるオブジェクトの候補のリストとなります(後述する通り、この時点ではコレクションの対象となると確定したわけではないです)。  逆に gc_refs が1以上であれば、それは現在使用されているオブジェクトであると判定できます。この場合、この世代以外の箇所からも参照されていると判断できるため、この時点でガベージコレクションの対象からは外れます。  下記の例だと、gc_refsが1以上であり確実に利用されていると言えるのは A のみです。ただし、gc_refsが0である unreachable のリストの中には、確かにコレクション対象となるべき D と E も存在しますが、 A から利用されていて消されるべきではない B と C も存在します gc_refsが1以上であれば外部からの参照が確実にあると言えて、そうでなければ到達不能な可能性がある  次に、 gc_refs > 0 のリストのオブジェクトでイテレーションを回して、それぞれのオブジェクトが持つ参照の中に、 gc_refs == 0 のリストに入れられたオブジェクトがないかを探します。  もし gc_refs が0であったとしても(=オブジェクト間の循環参照のみによって参照されていても)、消してはいけないオブジェクトによって参照されているのであれば、そのオブジェクトはまだ利用されていると判断できます。  元々 gc_refs が0である削除候補のリストに入っていたとしても、上記のチェックでまだ利用されていると判断された場合、 gc_refs が1以上のリストの方に移動されます。  下記の例だと、 A からの参照があることにより B がcontainersに戻され、同様に B からの参照があることにより C が戻されます。 object Bからobject Cにたどり着いたので、Cのgc_refsを増やしてunreachableリストから除く  上のチェックが全て終わった後も、まだ削除候補のリストに入っているオブジェクト(図の例だとオブジェクトの D と E )は、削除されてメモリ解放がされます。 まとめ  このように、Pythonのガベージコレクションは基本的には参照数を元に行なっていて、それではカバーできない循環参照を別途探して削除しており、後者は重い処理であるため世代の概念を導入しています。  セーフィーではエンジニアを積極的に募集しています。気になる方は以下をご覧ください。 カジュアル面談から受け付けておりますので、お気軽にご応募ください。 safie.co.jp *1 : この記事でPythonという時、CPythonの3.12のバージョンのことを指します *2 : Cのコード内でオブジェクト名等は Long となっていますが、これは Pythonの世界的にはintのことを指します *3 : -5から257の間であるかを判定 *4 : 引用したコードはだいぶ省略していてかなり簡略化されているので、より詳細な実装が気になる方はコードの方を見てみてください *5 : dict型オブジェクトにおける実装は こちら
この記事は Safie Engineers' Blog! Advent Calendar 15日目の記事です はじめに こんにちは、あるいはこんばんわ。 第1開発部QCDグループ 小山と申します。 ついに24年も年末となり、今年も懲りず締め切り間際になってから筆を走らせている次第です。 今年のアドベントカレンダーですが、QCDグループの人数も計14名と増えできることも少しずつ増えましたので、改めて 24年版『QCDグループって何をしているグループ?』 をテーマに現状のQCDグループについて紹介をしたいと思います。 過去記事リンクはこちら engineers.safie.link 今回はスクショとかも少なく文字がいっぱいなので安眠効果抜群です。 はじめに 主な業務内容 実施しているテストの種類 テスト自動化に関して <リグレッションテストの自動化率> <リグレッションテスト自動化によって実現できた削減工数> <各プロダクトにおける自動テスト実行頻度> セーフィーのQAエンジニア=テスト/SETエンジニアのこと? <どんなバラツキがあるのか(一例)> その他 最近始めてみたこと セーフィーのQAエンジニアとは(まとめ) 来年度に向けた準備(MVVを決めました) <MVV> 主な業務内容 QCDグループですが通称QAチームといわれることも多いです。 大まかにテストに関わる部分を ”中心に” 担当しているグループということは変わりないです。 担当しているプロダクトとしては以下の図のようなものとなっています 上記一覧の中で、業務システム以外は概ねQCDとしてプロダクトの品質を向上すべく開発業務に携わっています。 基本的に、1メンバーに対し定期リリースのあるIoTデバイス系プロダクト・または新規プロダクトの複数を担当として受け持ってもらっているのが現状です。 プロダクト内でどういった業務(テストなど)をセーフィーのQAエンジニアが行っているかといいますと、プロダクトごとに差分はありますが概ね以下の内容となっています。 実施しているテストの種類 エンハンス内容に対する機能テスト 既存機能に対するリグレッションテスト(エンハンス時に設計したテストの簡易版) 上記をブラックボックステストとして実施しています。 非機能テストについては、プロダクトの要求・要件定義時にPdM側に協力は依頼しつつ、明記してもらえた場合は要求を満たせているかを各担当者ごとに保証するような形で行っていますが、体系的に非機能についても保証ができているとは言いづらいのが現状ではあります。 テスト自動化に関して 思い返せば3年前になりますね。 テスト自動化について動き始めた当初は、リグレッションテストの工数削減を目的としてテスト自動化を進めてきました(毎月楽になる未来を想像していた) しかしながらふたを開けてみると、月に一度実行しないとメンテナンスを短期間で終わらせないといけないこともあり、上手く削減できているという実感を得ることは難しかったです。 そこでリグレッションテストの実施工数削減だけでなく 毎日の成果物としてコミットされるエンハンスに対し自動テストを動かすことで、不具合を早期に発見しプロダクトの品質を向上させる 手動でしかできない所を注力してテストを実行できる環境を作る 上記を目的に加え自動テストのシナリオを大きく見直しました。 現在の実績として、リグレッションテストの自動テスト化の主要なプロダクトについては以下の進捗となっています。 <リグレッションテストの自動化率> <リグレッションテスト自動化によって実現できた削減工数> <各プロダクトにおける自動テスト実行頻度> 来期以降に関してはリグレッションテストだけではなく、影響範囲として漏れがちな直近でエンハンスがないプロダクトやソリューションも含め実装対象としていけるよう準備を始めています。 実行頻度についても現状より多くのプロダクトで毎日実行できることを目標に設定し、トラブルが発生してしまった場合に備え自動テストの実行結果をきっかけにして素早く異常に気付ける体制を築いていけるようにしていきたいと考えています。 自動テスト対象を増やし実行頻度をあげることで、リグレッションテストにかかる工数やエンハンスが暫くなかったプロダクトやサービスにかかる保守工数を下げ、改善活動や新規プロダクトに対するテストに注力できる体制を実現できるようになる(はず) セーフィーのQAエンジニア=テスト/SETエンジニアのこと? と、ここまで書いてきてセーフィーのQAエンジニアは テストエンジニア/テストマネージャー or SET(Software Engineer in Test)エンジニア のことなのかな?と思われてしまいそうですが・・・ いわゆる「良いプロダクト」を出すために様々なことを行うQAエンジニアとして、テスト活動だけでなく要件定義~リリース後のフェーズでプロダクト全体の品質を高められるように(担当者によってバラツキがあるのが現状ですが)活動を行っております。 <どんなバラツキがあるのか(一例)> ビジネスとして成り立つか(利益を生むことができるか) サポート部門の運用を踏まえ、特殊対応をしなくてよいか PoC(概念検証)実施時、何に気を付けなければならないか、目的がブレていないか 上記については体系的な知識の習得方法があるわけではないため、個人の経歴・経験・センスによって品質保証活動での粒度がバラついてしまっています。 このバラツキに関しては来期以降も解消に向け継続して取り組みたいと考えています。 対応案の一つとして、最近QAエンジニアとしてセーフィーに加わってくださったメンバーの方が、各フェーズで行ったほうがいい/注意しなければならない指針と観点を作成してくれていますので、近いうちに発信してくださるはず・・・! その他 最近始めてみたこと 最近ですが社内ツールのエンハンスを開発しているプロダクトチームに対して「品質とは?」というテーマで勉強会を行った方がいます。 こういった啓蒙活動はプロダクトだけでなく、開発本部全体ゆくゆくは営業本部なども含め全社に対して行っていけるようにグループとして準備していきたいと考えています。 ※参考:狩野モデルとは?5つの「品質」や、導入するメリット・注意点を解説 https://service.shiftinc.jp/column/10933/ セーフィーのQAエンジニアとは(まとめ) 現状のセーフィーのQAエンジニアは、基本的なテストエンジニア/テストマネージャーとしての経験を活かし、 『テスト活動やE2Eテストの自動化やプロセスの改善などの品質を向上させるための様々な活動 を各自の希望に沿って行うエンジニア』 です。 今後はテスト活動以外で様々な品質を向上させるために”改善活動”を行い、より良い・価値のあるプロダクトをお客様に迅速に提供できるように来期からは準備を進めていきたいと考えています。 来年度に向けた準備(MVVを決めました) 急に話の内容が変わりますが、月日は早いもので私が入社してから3年が経過しおかげさまでQCDグループの活動領域も人員も増えてきました。 (業務委託メンバー合わせ14名、25年からは計16名) 様々な考えや経歴を持つ方がいて、個性豊かでいいグループになってきたのではないかと個人的には思っております。 そんな中、グループとして1つの共通認識があると業務を進めやすいという話がメンバーから上がりましたので、組織開発を担当するエンジニアリングオフィスとも相談しつつQCDグループのMVV(Misson Vision Values)を策定することができました。 ※策定までの過程・苦労は25年内にQCDのグループリーダーが書いてくれるはずです。 <MVV> Misson : 高品質・適正価格の「映像ソリューション」を提供し、あらゆる産業の現場DXを実現する。 Vision: 創る組織で開発における品質課題に目を向け、品質文化に根ざした改善活動をおこなえるようにする。 Values : より良い品質にしていくために品質とは何かを定義して開発本部全体に広げる。 広げるためにも開発プロセス改善をする(もしくはしてくれという提案をする) 品質改善をし続けるために新しい技術やプロセスを取り入れ1人1人が成長する 2025年からはMVVに沿ってセーフィーという会社の品質を向上させるため真摯に品質改善を続け、グループ一丸となってテストだけではなく改善活動に取り組みたい ・・・と目標を宣言させていただきます。 どういった活動をしてどういう効果があった/効果が出にくかったのかは、来年の年末に施策と共に振り返ることができればなと思っております。 来年も価値のあるプロダクトをお客様に届けられるようにテスト及びそれ以外の領域で活動できればと思います。
この記事は Safie Engineers' Blog! Advent Calendar 14日目の記事です はじめに はじめまして、サーバーサイドエンジニアの大町です。2024年も早くも年末になりました。年末ということで今年の技術トレンドを振り返りたくなってきたので、Qiitaのブログ記事1万本のデータを分析してみました。 この記事では、2024年はどのような技術が熱かったかを分析した結果とその分析方法について共有したいと思います。 はじめに Qiita APIを使ったデータ収集と分析 データ収集の方法 データ分析の方法 おまけ(バイグラム) さいごに Qiita APIを使ったデータ収集と分析 今回はQiitaの記事を分析したのですが、Qiitaというサービスを選んだ理由はその知名度とデータ収集の容易さです。 まず知名度ですが、Qiitaは技術に関する情報を共有するサービスとして有名な方だと思います。その会員は120万を超えるらしいです。日本のITエンジニアが144万人ということを考えると、多くのエンジニアがなんらかの形でQiitaというサービスを使っていることになります。 corp.qiita.com また、データ収集の容易さですが、Qiitaは外部の開発者向けに API を公開しています。それを使うことで整形された形でのデータを簡単に収集することができます。Qiitaは基本的に技術記事が投稿されますので、技術以外の記事のデータが混ざるということはなく、世の中のエンジニアがどういう技術を使っているのかを知るには適したサービスです。 データ収集の方法 Qiita API は 記事の収集や投稿など多くのAPIが用意されています。今回データを収集するのに使ったAPIは記事の一覧を取得するAPI GET v2/api/items です。このAPI を用いると、最新の記事から順番に記事の内容を取得することができます。APIのレスポンスには、タイトル・内容・タグ・いいねの数などのデータが含まれます。 パラメータとしてpage(ページ番号)とper_page(ページあたりの記事数)を指定することが可能です。 例えば、以下のようなURLをブラウザに打ち込むと最新の記事のデータ20件を取得できます。(制限はありますが、特に認証しなくてもデータを得られます) https://qiita.com/api/v2/items?page=1&per_page=20 ページあたりの記事数(per_page)を100にして、page=1 からpage=100 までのデータを取ることで1万本の記事を取得しました。2024/12/1に記事を取得したので最新のものは12/1です。最も古いものは2024/10/28でした。ということで、約1ヶ月分の記事1万本を収集することができました。 データ分析の方法 タグを使った分析 Qiitaは記事作成の際に、記事に対してタグを付与することができます。そのタグの使用頻度を求めることで今よく使われている技術がわかりそうです。 結果は以下のようになりました。 <タグの頻度TOP10> 順位 タグ 頻度 1 Python 1033 2 初心者 826 3 AWS 652 4 JavaScript 423 5 AI 297 6 React 271 7 Ruby 264 8 TypeScript 256 9 Docker 226 10 Rails 206 1位はPythonということで、他を圧倒していますね。5位に「AI」がランクインしていることからAI系の文脈でよく書かれていそうですね。 2位が「初心者」でした。これは技術トレンドを調べるという意図からは外れたデータですが、Qiitaは初心者向けに書かれた記事が多いということがわかります。今回集めたデータはQiitaのみなので、初心者向けの記事が多いといったデータの偏りはありそうです。 1位のPythonが他を圧倒したというのも初心者向けの記事が多いからかもしれませんね。 記事のタイトルを使った分析 次に記事のタイトルを使った分析を行いました。分析方法はタグの場合とほとんど同じで、タイトルの中でよく使われる単語を求めました。ただ、タイトルはタグのように単語単位で分けられているわけではないので、まずタイトルを単語に分割する必要があります。 この処理は「単語の分かち書き」と呼ばれます。さらに「です」や「ます」など今回の分析には不要なワードも除きたいですね。 このような処理には、形態素解析ツールが便利です。形態素解析は自然言語処理(NLP)の一つで、 文を最小の単位(形態素)に分解し、分解した形態素に対して品詞などの情報を付与する作業のことをいいます。形態素解析ツールには様々ありますが、今回は手軽に使えるJanomeというツールを使いました。 Janomeでは数行のコードで形態素解析ができます。 以下のコードで「私は東京で働くエンジニアです。」という文を形態素解析してみました。 from janome.tokenizer import Tokenizer # Janomeの形態素解析器を初期化 tokenizer = Tokenizer() title= "私は東京で働くエンジニアです。" # 文をトークン(形態素)に分割してリストにする tokens = tokenizer.tokenize(title) for token in tokens: print (token) コードを実行すると以下のような出力が得られます。 私 名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 東京 名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー で 助詞,格助詞,一般,*,*,*,で,デ,デ 働く 動詞,自立,*,*,五段・カ行イ音便,基本形,働く,ハタラク,ハタラク エンジニア 名詞,一般,*,*,*,*,エンジニア,エンジニア,エンジニア です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス 。 記号,句点,*,*,*,*,。,。,。 「私は東京で働くエンジニアです。」という文が小さな単位に分割されていますね。さらに品詞や読み方の情報なども付与されています。 今回は形態素解析を記事のタイトルに対して行い、その結果から単語の頻度を求めました。 まず、名詞のみを取り出して頻度を調べてみた結果が以下となります。 <名詞の頻度TOP10> 順位 単語 頻度 1 方法 746 2 AWS 408 3 AI 401 4 作成 381 5 Python 378 6 環境 341 7 化 308 8 開発 307 9 データ 276 10 ファイル 264 タグとはちょっと違ったデータが出てきました。一番多いのが「方法」ですね。2位と倍くらい違います。 全体的にみて技術用語より一般的な用語が多く出てきてしまいました。そこで、取り出す用語を固有名詞に絞ってみました。 <固有名詞の頻度TOP10> 順位 単語 頻度 1 AWS 336 2 AI 230 3 Python 205 4 API 136 5 C 124 6 React 108 7 Google 103 8 Rails 100 9 GitHub 99 10 Cloud 97 意図した感じのデータが取れました。AWSが一位です。 Cが5位にランクインしているのは少し驚きです。 7位がGoogleで10位がCloudなので、これはGoole Cloud ~ が多かったのかもしれません。 解析ツールの品詞情報の揺れ ここでAWSの頻度を見ていて気づいたのですが、ちょっとおかしいですね。 上の「名詞」で取り出した時のAWSの頻度は408ですが、「固有名詞」で取り出した場合は336と頻度が小さくなってしまいました。どういうことでしょうか。 同じAWSという単語でも「固有名詞」に分類されることもあれば、別の品詞に分類されるなど解析結果に揺れがあるのかもしれません。 そこで品詞の解析結果の揺れについて調べるために、「AWSについて学んだこと」「AWSを使ったインフラ構築」という2つの文でAWSの品詞に差分があるかJanomeを使って検証してみました。 「AWSについて学んだこと」の場合 AWS 名詞,固有名詞,組織,*,*,*,AWS,*,* <- 固有名詞として分類 について 助詞,格助詞,連語,*,*,*,について,ニツイテ,ニツイテ 学ん 動詞,自立,*,*,五段・バ行,連用タ接続,学ぶ,マナン,マナン で 助詞,接続助詞,*,*,*,*,で,デ,デ こと 名詞,非自立,一般,*,*,*,こと,コト,コト AWSが固有名詞で分類されています。 「AWSを使ったインフラ構築」 AWS 名詞,一般,*,*,*,*,AWS,*,* <- 一般名詞として分類されている を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ 使っ 動詞,自立,*,*,五段・ワ行促音便,連用タ接続,使う,ツカッ,ツカッ た 助動詞,*,*,*,特殊・タ,基本形,た,タ,タ インフラ 名詞,一般,*,*,*,*,インフラ,インフラ,インフラ 構築 名詞,サ変接続,*,*,*,*,構築,コウチク,コーチク AWSが一般名詞で分類されていますね。 同じAWSでも異なる分類のされかたをしていました。 一般に形態素解析では辞書のデータをもとに品詞を特定するので、AWSという単語は辞書に載っていなかったのかもしれませんね。 辞書に載っていない単語は未知語と呼ばれ、解析ツールが周囲の単語などから予測して品詞を求めます。その結果は周囲の単語に依存するので結果に揺れが生じることがあります。 結果の揺れに対する解決策は形態素解析ツールに搭載されている辞書にAWSという単語を追加すれば揺れなく分類可能ですが、今回は大雑把に傾向がつかめれば良いのでそこまではしていません。 おまけ(バイグラム) 最後にバイグラムについても出してみました。 *1 バイグラムとは2単語が連続した文字列です。一般にN単語またはN文字が連続した文字列をn-gramと言います。 なぜ出してみたかったかというと、先ほど出した名詞での頻度分析の結果を10位までしか表には出していなかったのですが、実は11位が「構築」という単語でした。 6位が「環境」なので、「環境構築」という言葉が分割されたのだろうと思い、それを確かめてみたくなりました。 <バイグラムの頻度TOP10> 順位 バイグラム 頻度 1 (環境, 構築) 131 2 (初心者, 向け) 80 3 (Next, js) 79 4 (生成, AI) 72 5 (対処, 法) 58 6 (文字, 列) 48 7 (Raspberry, Pi) 39 8 (ilasm, stack) 39 9 (stack, machine) 39 10 (解決, 方法) 38 やっぱり、環境構築でした。環境構築、たしかに大変ですよね。 生成AIはトレンディですね。このブログで調査するためのコードの大部分を生成AIで作成しました。 Raspberry Pi はよく聞くのでハードウェアへの入門として触ってみたいと思っているのですが、まだ触れられてません。来年こそは触ってみたいです。 生成AIやRaspberry Pi などのように、1単語の頻度ではTOPに出てこなかった単語が出てきたのでバイグラムも有用な情報ですね。 さいごに 今回の記事では、Qiita1万本の記事を使って技術のトレンドを調査してみました。普段、仕事ではデータと向き合うことはないので新鮮な気持ちになりました。 結果としては、AWSやPythonといった技術が(Qiitaコミュニティでは)人気だということがわかりました。 分析方法はタグや単語の頻度を求めるという単純な方法でしたが、それでも大雑把な傾向は見られたと思います。もし大量のテキストデータに出会う機会があれば、 単語の出現頻度を眺めてみてはいかがでしょうか。 *1 : 名詞に絞ってから、連続する2単語を出しているので正確にはバイグラムではない
こんにちは!セーフィー第2開発部のAndroidエンジニアのジェローム( @yujiro45 )です。 この記事はセーフィー株式会社 Advent Calendar 2024 の12月13日の記事です! 2024年2月1日、360°全方位を広範囲に撮影できる魚眼レンズを搭載した新しいカメラ「 Safie GO 360 」が公開されました。このカメラのプレビューをアプリで表示するために、3Dモデルを使用する必要がありました。この記事では、Androidでどのようにこれを実現したかを説明します。٩( ᐛ )و 半天球カメラの動画再生の表示する方法 1. 3Dモデルを表示する 2. テクスチャマッピング 3. 画角 ジェスチャー 最小・最大ズームの距離計算 回転と移動 タップ位置へセンタリング まとめ 半天球カメラの動画再生の表示する方法 Safieビューアーのストリーミング画面はまだCompose化されていないので、この記事ではxmlの実装を紹介しますが、composeでも同じロジックです。 1. 3Dモデルを表示する 👇10月に書いた記事に関する記事と深く関連していますので、まだ読んでいない方はぜひご覧ください〜。 engineers.safie.link 360°全方位を広範囲に撮影した映像を表示するために、半球の3Dモデルが必要です。カスタム3Dモデルはコードで作成することもできますが、あまりにも複雑で時間がかかるため、Blenderで3Dモデルを作成し、それを使用することにしました。 前回の記事で書いたように、SceneViewでは .glb と .glTF ファイルしかサポートされていません。どちらか一方を選択する必要がありますが、 .glb ファイルの方が軽く、効率的であるため、 .glb を使用することにしました。 sceneView.apply { // 3Dモデル作成 val modelNode = ModelNode( modelInstance = modelLoader.createModelInstance( R.raw.hemisphere ) ) // モデルの位置と向きの設定 modelNode.position = Position(x = 0f , y = 0f , z = 0f ) modelNode.rotation = Rotation(x = 90f , z = 180f ) // モデルを追加する addChildNode(modelNode) } 2. テクスチャマッピング カメラの動画再生ではExoPlayer使用しています。 ExoPlayerで再生している映像をもとに、3Dモデルのテクスチャにマッピングするために、 ARでビデオを表示した時 に作成した ExoPlayerVideoMaterial を使います。 sceneView.apply { // 3Dモデル作成 val modelNode = ModelNode( modelInstance = modelLoader.createModelInstance( R.raw.hemisphere ) ) // 動画再生によるExoPlayer作成 val exoPlayer = ExoPlayer... // ExoPlayerのカスタムVideoMaterial作成 val exoPlayerVideoMaterial = ExoPlayerVideoMaterial( engine, exoPlayer, materialLoader ) // モデルの位置と向きの設定 modelNode.position = Position(x = 0f , y = 0f , z = 0f ) modelNode.rotation = Rotation(x = 90f , z = 180f ) // モデルを追加する addChildNode(modelNode) } 上記のコードをそのまま使うと、SceneViewが3Dモデルの背面にテクスチャをマッピングします。そのため、コンテンツが逆方向にマッピングされるような見た目になってしまいます。 理想 現実 これを修正するには、GitHubで説明されているように、モデルのx 軸上のスケールを反転させる必要があります。 github.com modelNode.scale = Scale(x = - 1f , y = 1f , z = 1f ) いい感じですね!🥳 3. 画角 Safie GO 360カメラは、壁と天井の2つのpositionに設置できます。 横向き 下向き 撮影向きによって、3Dモデルを回転させる方法は異なります。壁に設置された場合、動きは頭の動き(上下、左右)に従います。 しかし、天井に設置された場合は、3Dモデルを宙返りしないような制限を入れる必要があります。 SceneViewのデフォルトの挙動を使用すると、3Dモデルをどの方向にも回転させ、任意のスケールでズームイン/ズームアウトができますが、ユーザーが3Dモデルの背面を見ることができないようにし、また無限にズームインやズームアウトできないようにした方がいいです。ソースコードを確認すると、SceneViewの CameraManipulator パラメーターはデフォルトで設定されていることになっています。 open class SceneView @JvmOverloads constructor ( // ... /** * Helper that enables camera interaction similar to sketchfab or Google Maps. * * Needs to be a callable function because it can be reinitialized in case of viewport change * or camera node manual position changed. * * The first onTouch event will make the first manipulator build. So you can change the camera * position before any user gesture. * * Clients notify the camera manipulator of various mouse or touch events, then periodically * call its getLookAt() method so that they can adjust their camera(s). Three modes are * supported: ORBIT, MAP, and FREE_FLIGHT. To construct a manipulator instance, the desired mode * is passed into the create method. */ cameraManipulator: CameraGestureDetector.CameraManipulator? = createDefaultCameraManipulator(sharedCameraNode?.worldPosition), // ... ) : SurfaceView(context, attrs, defStyleAttr, defStyleRes) https://github.com/SceneView/sceneview-android/blob/2969a2c5ef00e5e5a0bccb29053e33fd93fcc47d/sceneview/src/main/java/io/github/sceneview/SceneView.kt#L184-L199 望む結果を達成するには、それを削除する必要があります。しかし、ソースコードを編集できないため、SceneViewを継承した新しいクラスを作成し、 cameraManipulator のパラメーターを null に設定する必要があります。 class CustomSceneView @JvmOverloads constructor ( context: Context, attrs: AttributeSet? = null , defStyleAttr: Int = 0 , defStyleRes: Int = 0 ) : SceneView( context, attrs, defStyleAttr, defStyleRes, cameraManipulator = null // <--- ここ ) ただし、 cameraManipulator を null に設定すると、ユーザーのすべてのジェスチャーが削除されてしまいます。ということは、自分のジェスチャーを開発するしかありません。 ジェスチャー SceneView には、カスタムジェスチャーを実装するために setOnGestureListener() があります。 fun setOnGestureListener( onDown: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onShowPress: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onSingleTapUp: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onScroll: (e1: MotionEvent?, e2: MotionEvent, node: Node?, distance: Float2) -> Unit = { _, _, _, _ -> }, onLongPress: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onFling: (e1: MotionEvent?, e2: MotionEvent, node: Node?, velocity: Float2) -> Unit = { _, _, _, _ -> }, onSingleTapConfirmed: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onDoubleTap: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onDoubleTapEvent: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onContextClick: (e: MotionEvent, node: Node?) -> Unit = { _, _ -> }, onMoveBegin: (detector: MoveGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onMove: (detector: MoveGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onMoveEnd: (detector: MoveGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onRotateBegin: (detector: RotateGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onRotate: (detector: RotateGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onRotateEnd: (detector: RotateGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onScaleBegin: (detector: ScaleGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onScale: (detector: ScaleGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> }, onScaleEnd: (detector: ScaleGestureDetector, e: MotionEvent, node: Node?) -> Unit = { _, _, _ -> } ) https://github.com/SceneView/sceneview-android/blob/2969a2c5ef00e5e5a0bccb29053e33fd93fcc47d/sceneview/src/main/java/io/github/sceneview/SceneView.kt#L819-L839 イベントがたくさんありますが、半天球カメラの場合は以下の実装しか必要がありませんでした。 onMove → ドラッグ操作の時カメラを回転するために onScale → 最小・最大ズームのために onSingleTapConfirmed → プレイヤーのコントロールボタンを表示・非表示するために onDoubleTap → ダブルタップ位置へセンタリングするために 最小・最大ズームの距離計算 ズームイン/アウトの動きは基本的にカメラが軸に沿って移動することで実現されます。最大ズームの時には、カメラの位置を(0, 0)に設定し、最小ズーム(最大ズームアウト)の場合はカメラの移動範囲をモデルの外側が映らないように制限したいです。 最大ズームは大きな問題ではありませんでした。なぜなら、カメラの位置(x, y)が 0 より下に行かないように制限すれば上手くいきました。しかし、最小ズームの場合は、カメラが到達できる最遠のポイントを決める必要があります。これは球体の半径とカメラの FOV の設定を元に算出できます。 tan(FOV / 2) = r / d → d = r / tan(FOV / 2) 動画再生領域が縦長の場合に、最小ズーム状態で左右が見切れてしまいました。そのため、画面のアスペクト比を考慮する必要がありました。 // 球の半径を設定する private val aspectRatio: Float get () { val cameraViewAspectRatio = width.toFloat() / height.toFloat() return if (cameraViewAspectRatio >= 1f ) { // 横長の画面の場合は単位球の半径 1 をそのまま利用する 1f // 球体は半径を1にしておくと計算が楽 } else { // 縦長の画面の場合は、 FOV が vertical な設定の関係から、半径相当のサイズをアスペクト比を利用して変換する 1f / cameraViewAspectRatio } } // z 軸上のカメラと半球面モデルの最大距離 private val maxDistance: Float get () = aspectRatio / tan(Math.toRadians(FOV / 2f )).toFloat() 結果を見てみましょう! 横長 縦長 ズームできました〜🥳 回転と移動 Androidでは、 GestureDetector を使うと、ユーザーが画面にタッチした位置の(x, y)座標を取得できます。デフォルトでは、原点 (0, 0) はビューの左上隅にあります。 ユーザーがドラッグしている方向を特定するには、2つのポイント間のVectorを計算する必要があります: AB(x2-x1, y2-y1) の公式を使うと、ユーザーがスワイプした方向を特定することができます。 fun drag(from: Pair < Float , Float >, to: Pair < Float , Float >) { val previousPointX = from.first val previousPointY = from.second val currentPointX = to.first val currentPointY = to.second val dx = currentPointX - previousPointX val dy = currentPointY - previousPointY when (cameraControlMode) { CameraControlMode.CEILING -> { // カメラを回転する } CameraControlMode.WALL -> { // カメラを回転する } } } また、カメラの回転角度が制限内に収まるように調整しないといけないです。 球体の半径(r)と現在の距離(d)を元に最大回転角(α)を算出できます。 α = atan2(r, d) しかし、カメラの回転は画面中心を基準に行われるため、 FOV に応じた調整が必要です。 α = atan2(r, d) - (FOV / 2) /** * カメラの回転角度が制限内に収まるように調整を行う * * @param x x軸中心での回転角度 (deg) * @param y y軸中心での回転角度 (deg) * @return Pair<調整後の x 軸中心での回転角度 (deg), 調整後の y 軸中心での回転角度 (deg)> */ private fun adjustCameraNodeRotation(x: Float , y: Float ): Pair < Float , Float > { // 距離と球体の半径を元に最大回転角を算出 val rotationLimit = atan2( 1f , actualDistance) val rotationLimitDegree = Math.toDegrees(rotationLimit.toDouble()) // 球体外の写像を避けるために、 FOV を元に回転角をさらに制限 val xRotationLimit = if (rotationLimitDegree > (FOV / 2f )) { (rotationLimitDegree - (FOV / 2f )).toFloat() } else { 0f } when (cameraControlMode) { CameraControlMode.CEILING -> { // x 軸の縦回転は -90 deg から 0 deg の範囲となる val xAxisRotation = x.coerceIn(X_AXIS_ROTATION_LIMIT, X_AXIS_ROTATION_LIMIT + xRotationLimit) return Pair (xAxisRotation, y) } CameraControlMode.WALL -> { // FOV は縦方向を基準としているため、 y 軸方向の横回転はアスペクト比を元に変換した FOV 値を算出する val aspectRatio = width.toFloat() / height.toFloat() val hFOVHalf = Math.toDegrees(atan2(aspectRatio * tan(Math.toRadians(FOV / 2f )), 1.0 )).toFloat() val yRotationLimit = if (rotationLimitDegree > hFOVHalf) { (rotationLimitDegree - hFOVHalf).toFloat() } else { 0f } val xAxisRotation = x.coerceIn( - xRotationLimit..xRotationLimit) val yAxisRotation = y.coerceIn( - yRotationLimit..yRotationLimit) return Pair (xAxisRotation, yAxisRotation) } } } 横向き 下向き これもいい感じですね!🥳 タップ位置へセンタリング Safie Viewerでは、3Dモデルをダブルタップするとダブルタップした位置へセンタリングするという機能があります。3Dモデルの外側だと、普通に真ん中に最大ズームします。 それを実現するために、SceneViewの CollisionSystem を使用することで、ユーザーが3Dモデルをタップした位置を検知します。 setOnGestureListener( onDoubleTap = { motionEvent, _ -> val hitTest = collisionSystem.hitTest(motionEvent).firstOrNull { it.node == modelNode } if (hitTest != null ) { // 3DモデルのhitTest.pointの(x, y)をTapした! } else { // 3Dモデル外部をタップした } }, ) 横向き 下向き ただ、SceneViewのCollision Systemは四角いボックスとして機能されています。そのため、3Dモデルが四角形でない場合でも外側をクリックしてもタップイベントはモデルに当たることになります。 github.com 3Dモデルは半球形なので、外側にいる場合でも3Dモデルでタッチイベントがトリガーされてしまいました。 この問題を修正するために、3Dモデルの前に平面を配置しました。そして、ユーザーのタッチイベントのx座標とy座標に基づいて、それが3Dモデルの内側か外側かを知ることができます。 タップイベントは点が円内にあるかを確認するには、円の中心が位置 (0, 0) にあると仮定して、次の式を適用する必要があります: x² + y² ≤ d /** * 3Dモデルの外側をタップしているかどうか * @param x タップしたx座標 * @param y タップしたy座標 */ private fun isOutside3dModel(x: Float , y: Float ): Boolean { // 3Dモデルの外側をタップしている場合、nullにならない val outsideModelTouchPlaneHitTest = collisionSystem.hitTest(x, y) .firstOrNull { it.node == outsideModelTouchPlaneNode } ?: return false // ヒットテストの座標 (x, y) が半径 1 以内にある場合、3Dモデルのタップしている val isTouchInside = outsideModelTouchPlaneHitTest.point?.let { it.x.pow( 2 ) + it.y.pow( 2 ) <= 1 } ?: false return ! isTouchInside } Before After まとめ 半天球カメラの動画再生を表示する機能を開発するのは大きなチャレンジですが、たくさん勉強になりました。カスタムジェスチャーの開発は簡単ではなく、三角関数の知識が多く必要でした。しかし、SceneViewを使用すると、衝突システムなどの多くの便利な機能が提供され、希望するものを開発するのが楽になります。今回実装してみた結果もとても良く、スムーズです! この記事を最後まで読んでいただきありがとうございます。 また、この機能の開発に多大な協力をしてくれた同僚に本当に感謝しています。 このような面白い機能を一緒に開発したい方は、ぜひ以下のリンクをご確認ください! open.talentio.com open.talentio.com
この記事は Safie Engineers' Blog! Advent Calendar 12日目の記事です こんにちは、セーフィー株式会社でフロントエンドエンジニアをしている佐川です。 3Dプリンターでなんらかの入力デバイスや小物をつくることが趣味で、同好の士が増えることを願いながら部内のLTや深堀り会などで作ったものの話などをしています。 今回はその中で話していたことのひとつ、キーボードを作る話を書きたいと思います。実業務とは関係がなく、本当に趣味の話です。 3Dプリンターとはんだごてがあると、作れるものの幅が広がって楽しい パラメトリックモデリングによる3Dモデリングの容易さ あたりについて書いていきます。各工程について詳しくは記載していないため、もし気になったら調べてみてください。 キーボードを自作したい! キーボードを自作する 作りたいキーボードを考える パラメトリックモデリングによるケースのモデリング ファームウェアを書く 組み立てる&手配線 かかった金額 最後に キーボードを自作したい! ここ数年でキーボードを自作する行為はだいぶメジャーになってきた印象があります。 2025年3月に行われる キーボードマーケットトーキョー という自作キーボードをメインに扱う即売会では、協賛企業も含めると70を超えるブースが出展するのだとか…。世は大自作入力デバイス時代ですね。 自作キーボードは市販品とは異なり、採算度外視の自由さが魅力です。 游舎工房のWEBショップ を少し覗くだけで、あまりのバリエーションと各制作者の発想力にただただ震えます。最近だと、キーボードにトラックボールがくっついているのを見たときの衝撃たるや…。 さて、自作キーボードと言っても自作の方法には2種類あります。 売られているキットを買ってきて組み立てる 0から作る プリント基板を発注する 手配線 大体の自作キットは基板、外装(+ファームウェアを焼いたマイコン)がセットになっており、あとは必要な電子部品と好きなキースイッチ・キーキャップを用意すれば、プラモデルをぱちぱち組み立てていく感じでキーボードを作ることができます。 ただ、こんな自由度高く様々存在している自作キットといえど、100%自分が求めている要件を満たすものがあるとは限りません。当時、自分が求めている自作キットは存在していませんでした。ロープロファイルのキースイッチが使えて、格子配列の40%キーボードが欲しいだけなのに…。 妥協したものを買うのもなあ…と思ってキーボードの自作方法を見ていると、基本は電子回路を設計して外部にプリント基板を発注する方法がメジャーなようでした。 プリント基板は海外の制作会社に発注するとおよそ2000円~で作ってもらうことができますが、発注してから届くまでにある程度時間がかかります。(別料金を払えば翌日には出荷してもらえます) まずは作って、気に入らないところがあったら修正して…のサイクルを回したい自分としては、時間とお金が気になります。 また、発注時の最低ロット数は大体5枚なので、個人用に作りたいだけなのに4枚も余らせてしまうのはなんだか微妙という気持ちもありました。 もう少し調べてみると、3Dプリンターで印刷した外装にキースイッチをはめ込み、それらを手で配線していく方法で作っているものを見つけました。 なるほど、それなら100%自分好みに作れますし、ちょっと気に入らないところが出たらすぐに直して印刷してしまえばよいです。無限の可能性を感じます。 ScottoLong Handwired Keyboard 余談ですが、こちらは手配線にこだわるとここまで美しくできるのかと非常に感銘を受けたものです。 キーボードを自作する キーボードを作るにあたってやるべきことは、大まかにこれくらいでしょうか。 作りたいキーボードを考える ケースをモデリングする (回路図を書く) ProMicroやラズパイのようなマイコンボードを使う場合は単純なのであまり書きません マイコンボードに頼らないときは、マイコン、USBコネクタ、リセット・BOOTスイッチ、水晶発振器…などなど、登場人物が多いので書きます ファームウェアを書く フルスクラッチ QMK というキーボードファームウェアのベース 組み立てる&手配線 作りたいキーボードを考える 上のほうでも書きましたが、こんなキーボードが欲しいなと考えました。 キー数40% ロープロファイルスイッチ 格子配列 できるだけ本体を薄くしたい キーボードを作る前は、 hsgw氏のPlaid を使っていました。必要な電子部品たちが装飾も兼ねてきれいに配置されており、とても気に入っているキーボードです。こちらを使い始めてからは完全に格子配列に馴染んでしまい、他の配列に戻れなくなってしまいました…。 パラメトリックモデリングによるケースのモデリング 作りたいキーボード案をもとに簡単にラフを書いたあと、早速3DCADソフトを用いてモデリングしていきます。 ところで、自分は3DCAD、3DCGソフトを扱うのが不得意です。GUI上には膨大な数のアイコンやメニューが列挙されており、自分のやりたいことをスムーズに行うには、ソフトごとでの習熟が求められます。 自分の頭の中では「この平面に穴を等間隔で開けたい」「15mmの壁を足したい」など明確なのですが、それを行うための操作手順が煩雑で、作業に対するストレスがグングン溜まっていきます。 そんなとき、 OpenSCAD というコードベースでモデリングできるソフトウェアを見つけました。 実業務でも趣味でもコードを書いているため、こちらのアプローチにすんなり馴染むことができました。 たとえば「四隅に穴の空いたプレート」を作りたいと思ったら、以下のように書くことができます。 width = 10; length = 20; height = 1; difference() { cube([width, length, height], center=true); for (xpos = [-1, 1], ypos = [-1, 1]) { translate([xpos * (width / 2 - 1), ypos * (length / 2 - 1), 0]) cylinder(h=height, r=.5, center=true); } } 書きっぱなしで読みづらい状態ですが、参考までにキーボードをモデリングした際のコードを以下に置いておきます。 github.com ファームウェアを書く オープンソースで公開されているものがいくつかあり、そちらを用いることで簡単に自作キーボードのファームウェアを作成することができます。 有名なものは以下の2つかなと思います。詳しくはドキュメントなどをご参照ください。 QMK 一番有名なファームウェア Vial QMKベースで、WEBブラウザ上でのキーマップ変更機能を有していて便利 余談ですが、組み込み系の知識が乏しかったので、以下の動画を面白く見てました。 自作キーボードって組込みRustの入門にちょうどいいらしい #ch789 組み立てる&手配線 一気に端折ってしまいますが、モデリングしたケースをSTLファイルに変換して3Dプリンターで印刷するとこんな感じになります。(ネジ止め用のインサートナットを圧入済み…) ケースにキースイッチをパチパチはめて、キースイッチとダイオードを銅線で配線し… マイコンボード(今回は家に転がっていたRaspberry Pi Pico)とキースイッチを配線。 キーキャップはめて完成! (キーキャップの文字やアイコンは、テプラのマット透明テープに印字して貼っています。お手軽) かかった金額 部品名 金額(およそ) ダイオード38個 100円 Kailhロープロファイルスイッチ 38個 2600円 Raspberry Pi Pico 800円 キーキャップ 37個 1200円 3Dプリンタのフィラメントや、はんだとか、こまごま 500円 合計 4000円 …かかった時間を考えるとすごく安い!というわけでもないですが、キースイッチやマイコンなどのパーツは使い回せるので、作り直すときはより安価に作ることができますね。 最後に 後半はかなりふっ飛ばしてしまいましたが、3Dプリンターと手配線でキーボードを自作する話でした。 今後ですが、キースイッチを使いまわしたいときにキースイッチの足に直接はんだ付けしている場合、一度はんだを取り除く作業が必要になり非常に面倒くさいです。 なので付け外しが容易になるソケット(Kailh製やベリリウム銅のものなど)を用いてキースイッチのつけ外しが楽なように作りたいなあ…と考えています。 あとはロータリーエンコーダーをつけたり、一体型だけれど左右分離型のような配置のキーボードとか作りたいですね…。 最後までお読みいただきありがとうございました。年末年始のおともにキーボード作り、いかがでしょうか。
この記事は Safie Engineers' Blog! Advent Calendar 11日目の記事です はじめに みなさんこんにちは。法務部知財グループの永島です。 セーフィーでは様々な社内研修を行っていますが、今回は知財研修にスポットをあててお話したいと思います。 はじめに 知財研修ってなに? セーフィーの知財研修 入社時の知財研修 希望者は誰でも受講できる任意知財研修 知財研修への想い 知財研修を始めたきっかけは? 入社時の必須研修に知財研修(基礎編)を入れた理由は? 知財研修の対象者を開発や企画だけでなく全社員とした理由は? 知財レビュー(半年に1回)を始めたきっかけは? 経営層への知財レビューを対面で実施している理由は? 最後に、知財研修への想いを教えてください。 おわりに 知財研修ってなに? 知財研修はその名の通り、知的財産に関する研修となります。 一般的な知財研修では一例として以下のようなことを目的として行われることが多いです。 知的財産の理解促進 知的財産(特許、意匠、商標、著作権など)の基礎知識を学び、企業にとっての重要性を理解する。 法的リスクの回避 知財侵害を未然に防ぎ、他社の権利を侵害しないようにする。 知的財産の実務概要理解 出願、中間対応、調査、契約などの知財実務の概要を理解する。 うわー、なんだか難しそう、聞いているうちに眠くなっちゃいそう・・・って内容ですよね。 セーフィーの知財研修 セーフィーの知財研修は「知財をみんなで楽しもう!」をモットーにしています。 楽しみながらも、一般的な知財研修では得られにくい「実情に沿った考え方」と「ネタに気付くカン」を磨くことに重点を置いています。 知財研修を楽しんでもらうための私たちの3つのこだわりを紹介させてください。 その1:わかりやすい資料作り より楽しく受けてもらえるよう、イラストや図表を多用した工夫を凝らした資料作りに取り組んでいます! その2:極力専門用語は使わない 研修を気軽に受けてもらえるよう、専門用語の使用を極力控え、必要な場合はわかりやすい例えを交えて解説するよう心がけています! その3:受講者参加型 研修は基本的にWebで実施しており、研修途中にGoogle Meetの機能を活用したアンケートや、ワーク、ティーブレイクを取り入れることで、受講者がただ聞くだけで終わらない工夫をしています! ティーブレイクの雑談についてどのテーマの話が聞きたいかアンケートを取っている様子⇒⑨ラテアートはじめましたが一番人気でした(笑) セーフィーの研修の種類はいくつかあるのですが、そのうち、知財研修は「入社時の研修」と「希望者は誰でも受講できる研修」の2つがあります。 次はそれぞれの知財研修の内容についてお話します。 入社時の知財研修 セーフィーでは入社時の研修がとても充実しています!詳しくはこちらの記事でご紹介しています。 note.com 知財研修も入社時必須の研修となっているのですが、どんな内容かチラ見せ。 入社時の知財研修では主に以下のような内容を学びます。 知的財産権を取得する目的 特許、意匠、商標の基礎的な知識 セーフィーで取得した特許権、意匠権、商標権の紹介 入社時の研修では、一般的な知財研修に近い内容を扱いつつ、「知財は事業を守り発展させるために、開発や企画部門だけでなく全員が関わるべき重要なテーマである」というメッセージをしっかりと伝えています。 希望者は誰でも受講できる任意知財研修 任意知財研修は上期2回、下期2回の1年間計4回行います。今まで10回の任意知財研修が開催されています。 テーマ1:基礎編 テーマ2:特許編①特許文献の読み方 テーマ3:特許編②発明の捉え方/良い発明の条件 テーマ4:商標編 テーマ5:Safie知財の現状&今後 テーマ6:契約交渉編 テーマ7:2023知財レビュー/Safieの知財方針 テーマ8:著作権編 テーマ9:2024上期知財レビュー テーマ10:特許調査編 この研修は希望者のみの任意参加としていますが、セーフィーでは毎回リアルタイムで約100名の社員が参加しています。アーカイブ配信も行っている中でのこの参加人数は、セーフィーの社員数461名(2024年7月時点)の約4分の1に相当します。セーフィー社員の知的財産に対する関心の高さがうかがえますね! この中で異彩を放っているものといえば「知財レビュー」ですよね。 知財レビューは、上期・下期ごとに出願および登録されたセーフィーの知的財産権の内容を共有する研修です。経営層に関しては別途時間を設けて必須で実施しています。 任意研修のそれぞれの内容については別の回でじっくりお話させてもらえればと思います。 知財研修への想い ここからは、セーフィーで知財研修をやり始めた当事者である、知財グループGLの渡辺さんに、知財研修への想いを質問形式で聞いてみたいと思います。渡辺さんは、2022年1月に知財担当の1人目としてセーフィーに入社して以来、知財部門の立ち上げから知財の責任者として務めています。 知財研修を始めたきっかけは? (渡辺) 私が入社した当初、知財に対して理解のある人がほとんどいなかったからです。 知財活動を本格化していくには、周りの協力を得たり、予算を適切に獲得する必要が出てきます。そうなった時、後々苦労するだろうなというのが目に見えていたため、これは早い段階でテコ入れせねばと思いました。 入社時の必須研修に知財研修(基礎編)を入れた理由は? (渡辺) 基本的な知財リテラシーについては、常に全社員が身に着けている状態にしておきたいからです。 セーフィーは、毎月のように中途社員が入ってきて社員数が増え続けていますので・・・早い段階で確実に聞いてもらえる機会として、入社時の必須研修に目を付けました。 その副産物として、会社として知財を重要視している点はもちろん、知財部門の存在や知財担当の顔についても、初めから知ってもらえるという効果がありました。 知財研修の対象者を開発や企画だけでなく全社員とした理由は? (渡辺) 近年、知財が影響を及ぼす範囲が広がってきているからです。具体的には、従来から重要である「他社にマネされないようにする」だけでなく、近年は「お客様への訴求力を高める」「会社のイメージを上げる」「社員の士気を高める」「経営に新たな視点を与える」等。その様な時代に対応するため、というのが当初の理由でしたが、やっていく中で思わぬ部門に知財のファンが増えたりするのも、楽しみの1つだったりします。 知財レビュー(半年に1回)を始めたきっかけは? (渡辺) もっと知財を身近なものとして、当事者意識を持ってもらいたかったからです。知財レビューでは、実際に出願/登録になった件の事例紹介にも力を入れています。 特にセーフィーのようにSaaS系の技術分野では、当事者も「新しいものを生み出している」という自覚が少ない傾向にあるため、知財を通してそれを見える化し、社員の士気を少しでも上げられたらと思っています。 経営層への知財レビューを対面で実施している理由は? (渡辺) お忙しくてなかなか聞いてもらえないからです(笑)それに、対面ならディスカッションもできて様々な要望を拾うことができ、今後の知財の方針に生かせます。 最近では、全社向けの研修でやったクイズの結果を従業員の意識調査として、経営層にフィードバックすることもあります。この前、知財から距離が遠い筈のCCOや監査役から「実は毎回楽しみにしている」という声を聞いた時は、とても嬉しかったです。 最後に、知財研修への想いを教えてください。 (渡辺) とにかく、まずは知財を楽しんでもらえたらと。いいアイデアって、あれこれ楽しく考える中でたくさん生まれてくるものだと、今までの経験から感じてましたので。 知財に限らず、専門職の世界って素人には中々理解し難いですが、今後も工夫を凝らしていきますので、是非ご期待ください。 おわりに 「知的財産」って聞くと、なんだか専門的で難しそう…と思う人が多いと思います。私たち知財グループは知財研修で少しでもその垣根を取り除くことができればと考えています。 知的財産って、特別なスキルを持った人だけのものじゃなくて、社員全員がちょっとずつ関わることで、もっと強力な武器になります。部署の垣根を越えて学び合えば、きっとお互いに刺激を受けていいアイデアがどんどん生まれるはずです。 私たちはこれからも「知財をみんなで楽しもう!」をモットーにセーフィー独自の知財研修を実施していきます! セーフィーではエンジニアを積極的に採用しています。 興味がある方は是非下記サイトを一度覗いてみてください。 エンジニア職 | セーフィー採用サイト
この記事は Safie Engineers' Blog! Advent Calendar 10日目の記事です 「社内プチ表彰式」の写真 はじめに みなさんこんにちは。法務部 知財グループの永島です。 セーフィーでは、2022年に知財部門を立ち上げて以来、20件以上の特許が登録されています。 今回は、その中の1件について、上記表彰に応募したところ無事に受賞できましたので、お話いたします。 はじめに 地方発明表彰とは 受賞内容 どんな発明? 本件特許に込めた想い 「社内プチ表彰式」の様子 おわりに 地方発明表彰とは 地方発明表彰は、公益社団法人 発明協会が大正10年から開始した歴史ある表彰で、全国を8地方(北海道・東北・関東・中部・近畿・中国・四国・九州)に分け、各地方から生まれた優れた発明等を表彰するものです。 地方発明表彰の詳細(発明協会Webページ) https://koueki.jiii.or.jp/hyosho/chihatsu/chihatsu.html イメージとしては、「特許の甲子園」みたいな感じです。ただ、その地方大会とはいえ、特にセーフィーが応募する関東ブロックは激戦区で、例年、名だたる大企業が多数応募してきます。そこに、セーフィーのような数年前に上場したばかりのスタートアップが如何に切り込めるか・・・初応募は、まさに挑戦でした。 受賞内容 令和6年度 関東地方発明表彰 「発明奨励賞」   【発明名称】映像解析により人の滞留を検知するシステム  【特許番号】特許第7279241号  【受賞者】谷野 香菜 橋本 貴博 鬼城 渉 藤澤 真之 大友 裕明       柏木 直諒 松田 一輝 沖 総一朗 小嶋 幸恵 単に特許が取れただけでなく、それを第三者機関に評価してもらった結果、大企業と肩を並べて受賞者に名を連ねることができたという事実は、それだけで誇れることであり、会社にとって大きな自信となった筈です。 どんな発明? 特許第7279241号(以下、本件特許)は、 Safie One に搭載された AI-App人数カウント の1つである「立ち入り検知」に関するものです。 その概要については過去に こちらの記事でも紹介済み ですが、もう少し詳しく説明します。 近年、店舗のカメラで撮影した映像を現場のDX に活用する試みがあり、その中で本件特許が生まれました。 ざっくり言うと、上図に示したようなシステムにおいて、人の滞留を見える化するための技術です。 ポイントは、予め検知エリアおいて人の滞留を検知するための条件:①人数②滞留時間を設定しておき、実際にカメラで撮影した映像において①②の条件が満たされた場合に、そのタイミングを出力する(サムネイル表示/タイムライン上にオブジェクト表示)というものです。 利用シーンとしては、例えばレジ待ちが長くなっているシーンを可視化することにより、店員のシフトや接客方法を改善するのに役立ちます。また、例えば売場で特に混雑しているシーンを可視化することにより、売場のレイアウトや商品のラインナップを再考するのにも役立ちます。この様に現場のDXが進めば、店舗側の機会ロスが減り、お客様の満足度向上に繋がります。 さらには、店舗の省人化が図られることにより、日本の大きな課題である働き手不足の解消にも貢献することができます。 本件特許に込めた想い 近年、世の中では監視カメラの用途を現場DXに広げようとする動きが一部見受けられるようになってきたものの、まだ思うようには進んでいません。 その主な要因は、「記録された膨大な映像データの中から、見るべきシーンを特定するのが大変」という点にあり、これが映像データを利活用するうえで大きな壁となっています。 これを克服すべく、AIによる映像解析等に頼る企業もありますが、AIの精度を実用に足るまで上げるのは難しい場合が多く、AIを作るためのコストや時間も馬鹿にならないため、AIの社会実装はなかなか上手くいっていない、というのが現実です。 本件特許は、その様な現実を理解したうえで、多くの発明者が共に現場で議論を重ね、人が考えたロジックをシステムに組み込むことにより、映像データを利活用するための壁を突破しよう、という想いが込められています。 「社内プチ表彰式」の様子 発明協会の表彰式には参加できませんでしたが、その代わりに社内でプチ表彰式を行いました。 当日は、受賞者、森本CTOが参加し、表彰状の授与やスピーチ等で盛り上がりました! 表彰状授与の様子 受賞者の谷野さんのコメント 一言では語り尽くせないくらい困難なプロジェクトでした。当初は「Safie One」が売れるかどうか不安もありましたが、多くの方に選ばれる商品となり、大変嬉しく思っています。 今回の受賞は、チーム全員が一丸となって努力を重ねた結果が実を結んだものです。この成果を手にできたことを心から誇りに思います。 これからも機能改善を重ね、賢くなるカメラである「Safie One」をさらに進化させていきたいです。 森本CTOのコメント これまで、売れない商品に悩まされることも多く、まさに可能性を模索していた時期に「Safie One」は生まれました。これは、チームのやりきる力が生み出した成果です。 今回の受賞は、私たちの努力が評価された結果だと感じており、大きな喜びと誇りを感じています。また、このような表彰への応募は社会的な意義を広げ、セーフィーの認知度を高め、新たな可能性を切り開く大切な取り組みであると確信しています。 今後も、私たちはさらなる高みを目指し、果敢に挑戦し続けます。 おわりに セーフィーでは、上場後に知財活動が本格化しましたが、そこから1年目で種蒔き(出願)⇒2年目で収穫(登録)⇒3年目で外部評価(表彰)と、着実に進化してきました。 これは何といっても、現場で新しい技術がちゃんと生まれていたからです。特にSaaS系の業界においては、大して新しい技術は生まれていないだろう・・・と当事者含め思い込みがちですが、そうではないことが証明できたと思います。 今後もセーフィーは、地道な開発活動を通してイノベーションの現実解を見出し、日本の新たな未来を切り開いていきたいと考えています。 セーフィーではエンジニアを積極的に採用しています。 興味がある方は是非下記サイトを一度覗いてみてください。 https://safie.co.jp/teams/engineering/
この記事は Safie Engineers' Blog! Advent Calendar 9日目の記事です。 あいさつ こんにちは!セーフィーの井上と申します! 2023年11月に入社し、エンジニアリングオフィスの一員として日々業務に取り組んでいます。 今回はセーフィーの「エンジニアリングオフィス」という部署は一体何をしているのか、皆様への紹介も兼ねて取り組んでいることについてお伝えさせていただきます! あいさつ エンジニアリングオフィスとは? エンジニアリングオフィスのミッション ミッション達成するためには 取り組み内容 テックブランディング向上施策 テックブログ 次のステップへ 開発本部アイデアソン 2024年の進化過程 社内認知の向上と参加者の拡大 第3回での大きな進展 2024年の活動を通して得られた成果 社内認知向上が一定の成果を達成 オンボーディング・ネットワーキング構築の取り組みとして採用 まとめ 終わりに エンジニアリングオフィスとは? エンジニアリングオフィスは、取締役CTO・VPoE、各部室長や各部署と密に連携し、最高のプロダクトをつくるため、エンジニア組織拡大に伴い中長期の組織成長戦略を策定・実行しており、さらなる事業成長を見据え組織の強化を推進しています。 エンジニアリングオフィス発足に至った理由として、弊社のVPoEの谷口が記事を掲載しておりますので、こちらも是非ご覧ください! engineers.safie.link エンジニアリングオフィスのミッション セーフィーはVisionとして「映像から未来をつくる」を掲げています。 このVisionを実現するためには、中長期の戦略を実行できる組織へと常にアップデートし続ける必要があります。 セーフィーが掲げるVisionを実現するべく、エンジニアリングオフィスはプロダクトと人・組織の両輪を支えるために下記ミッションを設定しました。 エンジニア組織の未来を追求し、未来に起きるであろう課題も見通した解決エンジンとなる そのためには「ありたい姿」「こういう組織を作らなきゃいけないよね」というゴールを定め、目標への道筋を明確にすることが重要となります。 ミッション達成するためには ミッションを達成するべく、エンジニアリングオフィスではバックキャスティングの考え方を採用しています。 業務に着手するときは、始めに将来どういう姿になっていたいか・ありたい姿(ゴール)は何なのかを定義します。その後、現状の自分達の立ち位置を確認することで「ありたい姿と現状のギャップ(課題)」を確認します。こうして課題を明確にし、次に何をするべきかの道筋を明確にすることで、ゴールまでの道筋を迷うことなく進むことが出来ます。 取り組み内容 上記を踏まえた上で、実際どのように組織開発に臨んでいるのか?ということを、自分が担当している業務から3つピックアップしてご紹介いたします。 テックブランディング向上施策 まず、自分が担当している業務の1つとして「テックブランディング向上施策」があります。端的に説明しますと、社外のエンジニアからの認知を高める施策を担当しています。 じゃあイベントに参加したりブログを沢山出して社外にアピールしよう!そしたら認知あがるじゃん! ・・・等と、とりあえず目の前のタスクに掛かる前に、まずはバックキャスティングの基本である ・ゴール(あるべき姿・数年後どうなっていたいか) ・課題(あるべき姿と現状のギャップ) ・何をするべきなのか を腰を据えて定義しました。 ゴールを定義した後、そのゴールを達成できるよう道筋を明確にするため中長期計画を立て、ロードマップを策定しました。 ロードマップについて要約すると、以下のようになります。 FY24:社内認知が向上している FY25:社外認知が向上している FY26:セーフィーの組織文化が社内外に浸透している このテックブランディング向上施策の1つにテックブログがありますので、次項ではテックブログを例に具体的に何をしていたかを説明します。 テックブログ 上項で策定したロードマップにおいて、FY24は【社内認知が向上している】ことを目指しています。 テックブログは、世間にセーフィーの活動内容や取り組みを認知頂ける手段として最たるものだと自分は考えています。 しかしながら、そもそも活動内容や取り組みを執筆頂ける方がいないと、テックブログ自体が成り立ちません。 また、「情報を発信する重要性」が浸透していないと執筆して頂ける方も増えていきません。 そのため、上項で策定したロードマップに則り2024年の取り組みとして【社内認知の向上】に努めました。 例えば・・・ 開発本部全体会議にてテックブログの成果報告や成功事例の共有をすることで、テックブログがどの程度見られていてどのような反響があるのかをフィードバック テック系イベントの記事執筆例 Mobile Dev Japan #3 イベントを共同開催した話 - Safie Engineers' Blog! Findy さん主催のイベント「TechBrew in 東京 ~モバイルアプリの技術的負債に向き合う~」にて発表してきました - Safie Engineers' Blog! DroidKaigi 2024に参加してきました! - Safie Engineers' Blog! 勉強会や社内の取り組みをブログで展開して頂けるよう積極的にアピール 取り組み紹介記事の記事執筆例 AWS Startersに参加しました - Safie Engineers' Blog! We held our first English event at Safie, Safie English School! - Safie Engineers' Blog! 開発本部(エンジニア向け)社内アイデアソン始めました - Safie Engineers' Blog! スクラムチームの振り返りに Sailboat Retrospective を導入してみた - Safie Engineers' Blog! 執筆プロセスを改善して、より簡単に書けるプロセスを構築する等の運営プロセスの改善(進行中) 上記以外にも取り組んだ施策はありますが、こういった施策を実施した結果、2023年と比較して自ら執筆される方が増加し、記事執筆のご提案もご快諾頂ける文化が出来上がりました。 また、「この技術の裏側を記事にしたらウケそう」「この知見を是非外部に広めたい」等と記事ネタの提案も頂けるようにもなりました。 今回のアドベントカレンダーについても、「アドカレやります!!」と社内告知した際、皆様が積極的に立候補されたことにより、早い段階で全枠を埋めることが出来ました。 その他にも、他部署の方から「執筆したい」というお声もあがるようになりました。以下の記事は営業本部の松本さんに執筆頂いた記事です。 engineers.safie.link 上記のように、中長期のロードマップ計画に則り、施策を実施し続けた事で社内認知を向上させることができました。 次のステップへ 社内認知を上げるための活動を続け、執筆にご協力頂ける方が増えたことにより記事発信数が増加、テックブログ自体の露出が高まり社外認知も向上しました。 例えば、セーフィーがイベントブースに出展した際、ご訪問頂いた方々から「テックブログ見たことあるよ」等のお声がけを頂く機会が着実に増えています。 このように、ロードマップの各年度の計画が次の計画に繋がるよう設計していくことにより、理想の姿へ一歩一歩進むことができるようになります。 開発本部アイデアソン エンジニアリングオフィスでは、社内ネットワーキングの強化や、エンジニアのナレッジの向上を目的とした取り組みも行っています。 その一環として2024年にスタートしたのが開発本部アイデアソンです。 第1回開発本部アイデアソンの記事はこちら engineers.safie.link ここでも勿論バックキャスティングの考え方で企画・運営を進めています。 この開発本部アイデアソンの目指すべき姿として、以下を定義しました。 セーフィーの企画会議を通過し、正式なプロダクトとして稼働するアイデアを生み出せる場 上述した目指すべき姿を達成するためには、定期的に開催・都度アップデートを行い、業務時間を割いてでも参加する意義のあるイベントにしなくてはなりません。 そのための第一歩としてテックブログ項と同様、こちらもまずは社内認知を高める所からスタートしています。 いくら価値があるとアピールしても、参加者がいなければ開催はできません。 2024年の進化過程 社内認知の向上と参加者の拡大 アイデアソン開始当初はあまり興味を持たれておらず、参加者が少ない状態でのスタートとなりました。 しかし、「楽しかった」「学びが多かった」という好意的な反応や参加者の声が各グループ内で広まりました。 直近のアイデアソンでは、定員上限に近い人数が参加するほどになりました。 第3回での大きな進展 第3回アイデアソンでは、あるアイデアを正式な企画として実現したいという声が上がり、参加者たちによる企画会議に提案するためのチームが立ち上がりました。 この動きは、アイデアソンの目的である「正式なプロダクトとして実現可能なアイデアを生む」に直結しうる成果ともいえます。 2024年の活動を通して得られた成果 社内認知向上が一定の成果を達成 第3回まで開発本部アイデアソンを高頻度で開催したことにより、取り組み内容等の社内認知が向上しました。 「面白そうだから・学びがあるから参加してみたい」という声が他部署からも多く寄せられるようにもなり、社内認知の向上を達成できました オンボーディング・ネットワーキング構築の取り組みとして採用 開発本部アイデアソンは、部・グループを跨いだネットワーキングの向上に効果があると評価を社内で頂きました。結果、新入社員のオンボーディングコンテンツの一環として正式に組み込まれるようになりました。 今後は新入社員と既存社員を混成した人数で計画を組むことを意識し、より円滑なコミュニケーションを早期に構築することを狙っていきます。 上記の通り、2024年では認知向上やアイデアソンに参加することの有用性を証明することができました。 2025年からは、アイデアソンの目指すべき姿に到達できるように、より洗練されたアイデアや提案資料を生むことができるよう更にブラッシュアップを行います。 例えば、企画会議に提案できる資料のアウトプットやプレゼンの内容を充実させるためのレビュー会を実施したり・・・ アイデアがより実現可能で説得力のある形に仕上がるよう様々な施策を実施し、参加者全員に実りのある体験ができる場へとアップデートを行っていく予定です。 まとめ セーフィーはここ数年で事業・組織が大きくなったことで、組織のあるべき姿を中長期を見据えて戦略的に構築するフェーズに入りました。 個人個人の開発生産性を高めて成長を促すだけに留まらず、組織としての成長が重要になってきます。 組織を成長させ続け、新しい技術に挑戦できる組織にアップデートし続けるには、エンジニアリングオフィスが重要視しているバックキャスティングの考え方が活きてきます。 目指すべき姿は何か。現状と理想の姿のギャップ(課題)は何があるのか。実現するためには何をするべきなのか。 ・・・という事を押さえる事で、組織開発を実践できるようになります。 終わりに 本記事では、エンジニアリングオフィスについてご紹介させていただきました。 エンジニアリングオフィスでは、エンジニア組織の組織開発を牽引して中長期の組織戦略立案から実行を推進するメンバーを募集しています! open.talentio.com カジュアル面談から受け付けておりますので、気軽に応募いただければと思います!皆様のご応募、心よりお待ちしております! 最後までお読みいただき、ありがとうございました。
この記事は Safie Engineers' Blog! Advent Calendar 8日目の記事です はじめに こんにちは、第1開発部でサーバーサイドエンジニアをしている坂上(さかうえ)です。今回は2024年新卒エンジニア研修における成果発表と、その後についてお伝えします。 はじめに 研修の成果発表 サービス運用 運用方針 運用開始後 ナレッジ共有会 最後に 研修の成果発表 我々24新卒エンジニアは、エンジニア研修として「社内課題を解決するプロダクト開発」というテーマについて取り組みました。自身の体験や社員の方々へのユーザーインタビューを経て、異才ランチ(社員同士のランチ代を補助する制度)のマッチングをサポートするisai connectというプロダクトを開発しました。 プロダクトの詳細や、それまでの経緯に関する詳細は、今までの記事をご覧ください。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 ←本記事 研修の成果であるisai connectの成果発表は、2つに分けて行いました。 1つ目は、セーフィー社員全体に向けた発表です。 2024年新卒エンジニアがisai connectを作ったというインパクトを残し、認知してもらうことが主な目的でした。isai connectが解決する課題とターゲットの説明やサービス方針を伝え、より多くのユーザーに使ってもらえるような発表をしました。 2つ目は、開発本部に向けた発表です。 isai connectの開発の中でも以下の3つの内容に絞り、より詳細に説明しました。 ・2024年新卒エンジニアチームとしての開発の進め方 ・isai connectに実装した機能に対する技術的に工夫したポイント ・研修を通して成長した点と今後の意気込み 2つの発表を振り返ると、改めて発表の難しさを実感しました。研修内容が非常に充実していた一方で、限られた時間の中でその内容を効果的に伝えるためには、発表の目的を明確に設定し、それを達成するために内容を厳選する必要がありました。この整理や意思決定には時間を要し、難しさを感じました。 また、発表では、直感的に理解しやすい工夫としてスライドだけでなく、動画も活用しました。その結果、多くの社員に興味を持ってもらい、「興味が湧いた」「使ってみたい」といった前向きな感想をいただくことができました。 こうした声をいただけたことで、今回の発表で設定した目的を達成できたと感じ、とても満足しています。 この経験を通じて、限られた時間で効果的に見せる大切さを改めて学ぶことができました。 以降の章ではサービス運用と研修後の勉強会について説明します。 サービス運用 運用方針 isai connectを実際にセーフィーで使ってもらうために、サービス方針を定めました。 この運用方針で進めるにあたって、配属先であるグループの上長に許可をもらう形で、業務として運用していく形としました。 主な業務内容としては、以下5つです。 isai connect定例ミーティング 隔週30分 議題 サービスの利用状況確認 各自の取り組んだことの進捗確認 相談事項の確認 新規機能開発 ユーザーからの要望や開発メンバーからのアイデアを元に、新規機能についてSlackで開発メンバーと共有し、実装すべきとなった場合にチケットとして発行する バグ修正のチケットの優先順位を加味して、実装していく バグ修正 発生したバグやユーザーからあった要望、そして新たに開発したい機能をチケットとしてまとめ、スクラムマスターが優先順位づけを行ったものを、上から処理していく 主当番 2週間ごとに交代制 お問い合わせ対応 Slackの専用チャンネルを設け、isai connectの利用者からの不具合の報告や要望の一次対応をする 副当番 2週間ごとに交代制 主当番のお問い合わせ対応の補佐 従業員名簿更新作業(※月の最初の週の場合) セーフィーの社員名簿が月初に新しくなるに伴い、更新作業を行う 定期リリース作業(※月の最後の週の場合) 2週間で実装した内容をリリースする 運用開始後 サービス運用を開始すると実際に使用したユーザーから感想を多数いただきました。 isai connectがなければ、関わりがなかったであろう人とランチにいく良い機会になりました 誘いがきた時に、自己紹介が見れるので、事前知識があって嬉しいです さらに、この機能があったらもっと良いなというフィードバックまでいただくことができました。 日程調整の機能までついていたら、さらに手軽になる 誘う時に、誘われる側が異才ランチに行きたい気持ちがあるかどうか、わかるとより誘いやすい 一方、それと同時に以下のような障害対応にも追われました。 isai connectのアカウント作成時に利用規約に同意しようとすると画面が真っ白になってしまう 異才ランチが成立した時に、作成したSlackグループDMの文言が間違っている デバイスの電源を常につけておくと、フリーズしてしまう このように、実装の不備に加え、実際に運用することで初めてわかるような想定以上の不具合に追われ、配属先の業務と並行して対応を行いました。サービス運用を継続しながら引き続き改善を続けています。 また、2024年新卒エンジニアに運用の感想を聞いたところ、ポジティブな意見としては以下のような声がありました。 いろんな方から使ったよと言ってもらえて嬉しい 自分が開発した機能が、他の人に使われている様子が見れて、達成感がある 一方、今後の改善点としては以下のような声がありました。 本業務の合間にまとまった時間を取るのが難しい 開発中には出てこなかった問題が浮き彫りになる 研修時に担当していた分野以外のチケットを取りたかったが、運用となるとハードルが上がってしまう 運用開始から5ヶ月が経とうとしていますが、月に10件弱の異才ランチの実施がisai connectを通じて行われています。 今後は、本来の目的である価値を発揮するために、これまでの運用で得られたフィードバックや障害対応を活かし、更なる機能改善と安定性向上に注力していきます。また、運用を担うチーム体制の見直しを行い、各メンバーが無理なく役割を果たせるよう、業務の分担や支援体制を整えていきます。 ナレッジ共有会 無事にエンジニア研修を終えた後、配属先での実務が本格的に始まりました。研修を通じて築いた2024年新卒エンジニアのつながりを活用するため、私たちからの提案で「ナレッジ共有会」を隔週で開催しております。配属先で学んだ知識や経験を共有して、早く新しい環境に適応し、エンジニアとして成長することが目的です。 この会では、各回で2人の発表者が発表資料を準備し、1人15分ずつ、合計30分間のプレゼンテーションを隔週で行っています。テーマは配属先で得た知見や学びを中心とし、実務で役立つ技術的な内容から、業務効率化の工夫や課題解決のアプローチまで幅広く設定しています。 ナレッジ共有会は、新卒エンジニア同士のつながりを強化するだけでなく、他のメンバーの知識や経験から刺激を受け、新しいアイデアを得る場としても機能しています。今後もこの取り組みを継続し、互いに支え合いながら成長していきます。 最後に 2024年新卒エンジニアのメンバーは、多様なバックグラウンドを持つ仲間が集まりました。その中で、意見が食い違ったり、進め方がなかなか決まらなかったりと、数多くの困難にも直面しました。しかし、チームで協力しながら乗り越え、一つのプロダクトとして形にすることができたことに大きな達成感を感じています。同時に、isai connectをさらに成長させ、多くの方々に価値を届けられるサービスへと進化させていきたいという思いも抱いています。 また、このプロジェクトを通じて築いた仲間とのつながりを大切にし、互いに切磋琢磨しながら成長を続けていきたいと思っています。これからも挑戦を重ね、セーフィーの一員としてさらなる価値を創出していきます。
この記事は Safie Engineers' Blog! Advent Calendar 7日目の記事です はじめに こんにちは。第4開発部でデバイスエンジニアをしている集路(しゅうじ)です。 今回は2024年新卒エンジニア研修におけるデバイス分野の開発についてお話しします。 はじめに セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介 開発したデバイスの概要 デバイス機能の決定まで 開発上の苦労・工夫点 今後の展望 最後に セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介 セーフィーでは全体研修後、約3ヶ月のエンジニア研修があり、エンジニアとしての基礎を学びつつ、社内課題を解決するプロダクトを開発しました。 課題選定から開発言語、体制まで全て自分たちで決める形式で、私たちは「isai connect」を開発しました。これは、他部署の方とランチに行くと会社がランチ代を負担してくれる「異才ランチ」制度をより活用し、活発化を図るためのプロダクトです。 詳しくは以下の記事でご覧ください。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 ←本記事 2024年新卒エンジニア研修-新卒研修の成果発表とその後 セーフィーではカメラを扱っていることもあり、新卒エンジニア研修でも組み込み開発の領域も体験するため、 RaspberryPi とカメラを用いるという条件がありました。 我々の開発したプロダクト「isai connect」では、Webの開発が主であり、どのようにしてデバイスをサービスに結びつけるのか、など苦労した点も多くありました。開発したデバイスの概要や、その紆余曲折についてご紹介します。 開発したデバイスの概要 まず、開発したデバイス「Receipt Sender」の概要・機能について説明します。 このデバイスでは、名前の通りレシートの画像を個人のSlackに送信します。ランチから帰ってきた人がすぐにレシートを送信できるよう、オフィスの出入口付近2か所に設置しています。 異才ランチ後の経費精算では、経費申請の際にレシートの画像を登録する必要があります。その際、入力の都合上PCでの申請が便利なため、一度手持ちのスマートフォン等でレシートの写真を撮り、Slack等で自分宛てに共有している人が複数いました。この手順をもっと手軽に行えないか、ということで生まれたのがこの「Receipt Sender」です。 Receipt Senderを利用したレシート送信は以下のような流れです。 1. 顔認証ログイン 2. レシート撮影 3. レシート送信 以下で Receipt Sender の利用方法をご紹介します。 ~利用方法~ 1. 「始める」ボタンから顔認証を実施 2. 本人であることを確認し、「レシートの撮影に進む」ボタンからレシート画像を撮影 3. 撮影した画像を確認し、「DMに送信する」ボタンからSlackへ送信 4. Slackに画像が届いていることを確認 デバイス機能の決定まで デバイスの具体的な機能や活用方法には悩みました。どのようなプロダクトを作っていきたいかのアイデア出しの時点においても、デバイスをどう活用するのか?を念頭に置いて考える必要があり、なかなかアイデアが定まらない一因となっていました。 アイデアが固まり、デバイスの用途は「レシート送信から経費申請までの手間を減らす」ものを作ると決まりました。当初の計画では、経費精算サービスと連携して、レシートの送信から申請までをスムーズに行えるよう整備することを考えました。 しかし、いざ社内で相談してみると、サービスとの連携はNGという結果に…。経費に関係し、セキュリティ面も考えての結果だったため、当初より範囲を小さくして開発することとなりました。そこで、ランチから帰ってきてすぐにレシートの送信ができるだけでも申請の手間が減ると考え、その機能を作成しました。 ただレシートを送信する機能だけではなく、デバイスとして設置する価値を高めたほうがよいとのご指摘もありました。どのように活用するかを考えた結果、トップページにおいて累計・月間それぞれのコネクト数を表示し、どの程度使われているのかを知ることができるように工夫しました。 これにより、isai connect の状況を社員の方が簡単に見れるようになり、興味を持っていただくきっかけを作ることができたのではないかと考えています。 開発上の苦労・工夫点 今回のデバイス開発では、実装の手軽さから Python を選択しました。GUIの作成は tkinter を使用しています。 2台のカメラの制御 図のように、Receipt Sender では、顔認証用のカメラ(市販のUSB-Webカメラ)と、レシート撮影用のカメラ(RaspberryPi用カメラモジュール)の2台のカメラを同時に使用しています。 1つのアプリ内でこれらのカメラを別々で動作させる際、顔認証用のカメラ映像は映らないがレシート撮影用のカメラ映像は映る…などの問題が生じました。 各カメラの映像を扱うのに、顔認証用ではOpenCV、レシート撮影用では picamera2 および libcamera と別々のライブラリを使用しており、複雑化していた点も要因の一つでした。 最終的には、カメラ毎にスレッドを割り当てて処理することで解決しました。 顔認証 顔認証の機能を実装するにあたり、2023年度の新卒エンジニア研修で開発された「librarian」の顔認証用APIを利用させてもらうことにしました。顔認証の実装となると大きく時間もかかるため、23卒の先輩方に利用させてほしい旨お伝えしたところ快く許諾してくださいました。 結果として、先輩から我々へと新卒エンジニア研修の成果が繋がる非常に良い要素だったのではないかと思っています。 一方、顔認証の部分では、複数人で連続で利用する際、操作している人ではなくその前に操作した人として認証されてしまう不具合もありました。下図に示す通り、顔認証画面からの画面遷移時にその時点でのカメラ画像が保持されたままになっており、再度顔認証画面を開いた際に保持された画像が一瞬表示されてしまうことが原因でした。 この不具合に対し、顔認証の処理を走らせる間隔を調整してみるなど様々試しながら修正を行いました。時間はかかってしまったものの、最終的には顔認証画面遷移時に明示的に認証情報をリセットすることで解決しました。 専用のケース作成 社内に設置するにあたり、RaspberryPi本体を隠しつつレシート撮影用カメラを固定するケースが市販で存在しなかったため、プラ板を用いて自作しました。本体に合う大きさに切って張り合わせるだけの単純な作業ではありましたが、ぴったり合うものを作成でき、むき出しのまま置くことを防げています。 今後の展望 今後は、常時稼働していることを活かし、利用されていないときは社内への周知事項を表示させるデジタルサイネージのような機能を開発することを考えています。また、このデバイスの価値を最大限高めるには、やはり経費申請をより簡単に行えるよう改善していく必要があると思っております。 最後に 私自身、チーム開発の経験がなく、プログラミングも研究で扱った程度(しかもFortranのみ)といった状態でした。技術的な不安が多くある中、必死に勉強しながら同期のメンバーや先輩の社員の皆様にもご協力いただき、最終的にはデバイスの開発部分の多くを実装することができました。デバイス以外にもフロント分野に挑戦することもでき、未経験でも多くの経験を積める有意義な研修であったと感じています。
この記事は Safie Engineers' Blog! Advent Calendar 6日目の記事です。 はじめに  こんにちは。第2開発部でフロントエンドエンジニアをしている東條です。今回は、2024年新卒エンジニア研修におけるインフラ分野の開発についてお話ししたいと思います。 はじめに セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介 導入 定期実行について バージョン更新とメンテナンスについて バージョンが更新されているのかどうかが判断できない バージョン更新中にアクセスするとエラーになる Slackへの通知が確認できない 感想 セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介  セーフィーでは全体研修後、約3ヶ月のエンジニア研修があり、エンジニアとしての基礎を学びつつ、社内課題を解決するプロダクトを開発しました。課題選定から開発言語、体制まで全て自分たちで決める形式で、私たちは「isai connect」を開発しました。これは、他部署の方とランチに行くと会社がランチ代を負担してくれる「異才ランチ」制度をより活用し、活発化を図るためのプロダクトです。詳しくは以下の記事でご覧ください。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 ←本記事 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 導入  今回、個人としてはじめて IaC( Infrastructure as a Code ) によるインフラ構築を体験しました。使用したツールは Terraform です。そもそも記法やどんな仕組みで動いているのかも知らなかったため、他のインターン生がインターンのときに書いていたコードや公式のチュートリアルのコードとにらめっこしながら概要を把握していきました。  最終的な全体構成としては、 フロントエンドアプリ S3 にビルドしたファイル群を配置 CloudFront から配信 APIサーバー ECS Fargate でコンテナを立ち上げ DB Aurora MySQL のようになっています。 AWS のインフラ構成図  最初は Single AZ で組んでいたのですが、インフラの構築をしている時期にたまたま AWS の方が入門者向けにハンズオン形式で講義してくださる貴重な機会があり( https://engineers.safie.link/entry/safie_aws_starters_report )、そのなかで Multi AZ 構造をとることで可用性を担保できると教えていただいたので早速取り入れました。  余談ですが、AWS の DB 系の Multi AZ 構成では Writer (書き込み専用)と Reader (読み取り専用)が別れていて完全な2台構成になっていないということを初めて知りました。書き込みが複数の AZ で発生すると一貫性が崩れるので当たり前といえば当たり前ですが賢いですね。Writer が死ぬと Reader のうち1台が Writer に昇格するのは動物の生存本能みたいでとても合理的でおもしろい仕組みだと感じました。  この記事内では、インフラの構築および運用の中で工夫した点や苦労した点についていくつか紹介させていただきたいと思います。 定期実行について  24新卒チーム内で機能開発を進めていく中で、実際にコネクトしてお誘いのメッセージをおくったはいいものの返事をくれなかったときはどうすればいいのかという問題が浮き彫りになりました。ここで、3日経ったところで自動でキャンセルする機能の開発を行うことになりました。API のサーバーの機能的には、定期的に DB を見てコネクトの作成日から3日経っているかを判定してキャンセル処理をすればよいのですが、本番環境で定期実行をどうするかに結構悩みました。  最初思い浮かんだのは cron スケジュールですが、そもそも Fargate では Linux 自体を起動しているわけではなく Python:3.11-slim のイメージを起動していますし、今後オートスケーリングなどをした場合は無駄に複数回処理を回すことになりバグが発生する可能性もあり却下しました。  そこで、AWS のサービス自体でなんとかできないかと色々調べ回った結果2つ選択肢の目星をつけました。 Lambda を使用して Aurora MySQL を直接操作する Event Bridge Scheduler で ECS タスクを定期実行する  最終的には EventBridge Scheduler で ECS タスクを定期実行する方針で決めました。理由としては、学習コストが少ないことです。これは、チーム内のスケジュール的な問題ではありますが、この定期実行のタスクに取り掛かったのが最終のスプリントであったため、学習コストを抑えて短期で実行可能な状態まで持っていく必要がありました。  実際の実装としては単純なもので、API サーバーで定期実行用の API エンドポイントを用意して定期実行 ECS の中では curl イメージを使用してただその API を叩いています。このような実装にした理由は、開発において API サーバーとキャンセル機構が分離すると保守性が下がってしまうためです。 バージョン更新とメンテナンスについて  今回のインフラ構築では、アプリケーションの特性上アクセス頻度が高くないことと、集中した開発が研修中だけであることから、AWS のコストを抑えるためステージング環境を用意せず本番環境のみの構成としています。その中で困ったことが3点ありました。 バージョンが更新されているのかどうかが判断できない バージョン更新中にアクセスするとエラーになる Slack への通知が確認できない  それぞれどのように解決したかをご紹介します。 バージョンが更新されているのかどうかが判断できない  これはステージング環境があっても起こることだとは思うのですが、イメージやファイルをあげてアップデートしてもそれが反映されているのか、エラーで反映されていないのか、が判断できませんでした。フロントエンドだとデザインの修正や機能の修正が目に見えるのである程度問題はないのですが、とくにサーバーサイドのバグ修正や DB のスキーマ修正などは API を叩くまでは判断できないためデプロイ時に判断できる仕組みが必要でした。  対策としては、ビルド時にバージョンを入れ込む仕組みを導入しました。デプロイする際に CI で自動で付与していたバージョン( vx.x.x )を指定してビルド&デプロイすることで外部から簡単にバージョンを確認できるようにしました。  具体的には、フロントエンドではビルドするコマンドにバージョンを渡すことで meta タグにバージョンが埋め込まれるようにしました。バックエンドではイメージのビルド時に Dockerfile で環境変数を ARG と ENV を用いて固定することで Swagger UI に表示し、それぞれバージョンの表示を実現しています。以下画像は v1.6.0 状態を確認した際のものです。 フロントエンド バックエンド  このあたりはまだデプロイを含めて自動化できていないため、今後の課題として CI/CD を充実させたいです。 バージョン更新中にアクセスするとエラーになる  フロントエンドのアプリケーションはキャッシュを削除しない限り以前のバージョンが配信されますし、バックエンドも更新をかけて新しいコンテナが起動してから置き換わるのでそれぞれがエラーを吐くことはないのですが、デプロイ自体のラグが発生するためフロントエンドで叩いていた API が新しいパラメータを要求するようになっていたり、逆も然りが置きます。  そこで対策としてメンテナンス期間を導入しました。想像つきやすいのは銀行系のアプリなどである「◯月◯日◯時から◯時はメンテナンスのため一部機能がご利用できない可能性があります」だと思います。AWS の ALB に固定 503 Service Unavailable エラーを返すルールをメンテナンス中のみ追加し、フロントエンド側は 503 Error を返却されたときにメンテナンスページを開くように実装することでデプロイ中の数分間のみサービスを停止しています。 メンテナンス中に表示される画面 Slackへの通知が確認できない  本番環境の API を叩くと実際に DM が届いてしまうので、Slack からのアクションを確認できないという問題が発生しました。プレリリース前の開発中はテスト用の Slack チャンネルを作成することで実際に DM を飛ばすことなく確認できたのですが、本番環境ではそうはいかないので代替案を考える必要がありました。  色々考えた結果、最終本番環境に Slack App のテスト環境を作りました。具体的には、Slack App を開発検証用に DM 権限を制限した状態で作成し、図のように開発検証用の Slack App には本番環境のテスト用エンドポイントを指定、テスト用エンドポイントへのリクエストボディをそのままテスト用チャンネルに送信するように実装しました。こうすることでテスト用チャンネルに送られてきたリクエストボディをローカル環境で立ち上げている API サーバーに送信することができます。これによりで Slack のアクションを確認することが可能になりました。(ただし、参考として ngrok などを使ってローカル環境で Slack App 自体をモックすることは可能なようです) Slack通知のテスト環境概念図 感想  今こうして振り返るとこんな感じに構築していたらもっと楽だったかもしれない、今ならこうするといった反省点が山盛りではあるのですが、個人開発でも触ってこなかったインフラの分野についてチーム開発を通して試行錯誤しながら触れることができたのは良い経験だと考えています。また、新卒でのチーム開発を通して配属されたあとも未だに一緒にお昼ごはんを食べているくらい仲が良くなったので、何かあったときに気軽に相談できる仲間を社内に作れたという点はとても重要でした。
この記事は Safie Engineers' Blog! Advent Calendar  5日目の記事です。 自己紹介 こんにちは。セーフィー株式会社 開発本部所属の佐々木 大翔(ささき ひろと)と申します。今回は、我々が行った2024年度新卒エンジニア研修における、フロントエンド開発についてお話ししたいと思います。 自己紹介 エンジニア研修と開発したプロダクトについて 活動内容 画面定義 画面実装 使用技術 工夫した点 テストコード実装 テスト方針 テストツール 気をつけたこと・学んだこと 試行錯誤した部分 改善例 トップ画面の改善 プログレスバーの追加 アプリのフロントエンド開発をしてみて エンジニア研修と開発したプロダクトについて セーフィーでは全体研修後、約3ヶ月のエンジニア研修があり、エンジニアとしての基礎を学びつつ、社内課題を解決するプロダクトを開発しました。課題選定から開発言語、体制まで全て自分たちで決める形式で、私たちは「isai connect」を開発しました。これは、他部署の方とランチに行くと会社がランチ代を負担してくれる「異才ランチ」制度をより活用し、活発化を図るためのプロダクトです。詳しくは以下の記事でご覧ください。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 ←本記事 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 活動内容 今回のフロントエンド開発で行ったことは以下の通りです。 画面定義 実装 テストコード実装 以上の工程について、それぞれ説明していきます。 画面定義 ここでは、Figmaを用いてアプリ内のページ遷移や各ページのレイアウト、UI・UXデザインを定義した画面仕様書の作成を行いました。 フロントエンド開発の担当者は決まっていたものの、画面仕様書を作成した際にはチームメンバー全員で確認を行うなどして、チーム内できちんと合意をとった上で画面定義を行いました。 また、我々の開発はアジャイル開発をベースにしていたので、スプリントごとに画面定義を行いました。アジャイル開発については こちらの記事 をご覧ください。 isai-connect-画面デザイン 画面実装 制作した画面仕様書をもとに、コーディングを行いました。 使用技術 今回の実装で使用したものは、以下の通りとなっております。 プログラミング言語・・・TypeScript フレームワーク・・・React ビルドツール・・・Vite UIライブラリ・・・Material UI コード整形関連ツール・・・ESlint, Prettier 基本的に、ドキュメント量が豊富であり、サポートが現在も継続的に行われているツールを採用しました。また、実際にセーフィーで活用されている言語やツールを用いることで、配属後の学習コストを下げる狙いもありました。 工夫した点 Figmaで定義したフォントサイズやコンポーネントが画面上に反映されやすくなるよう、Figmaの設定に忠実に倣って、Material UIのコンポーネントのテーマを定義しました。 例えば、上の画像に定義されている黄色の「デフォルトボタン」では、ColorにSecondary、VariantにContainedが定義されています。 isai-connect-ボタンデザイン isai-connect-デザイン設定 この設定をコード上では以下のように定義しました。これによりコンポーネントに”color=’Secondary’”、”variant=’contained’”というような形で変数を指定するだけで、この黄色のボタンをFigmaの定義通りに表現することができます。 components : { MuiButton : { variants : [ { props : { size : "small" } , style : { fontSize : 10 } } , { props : { size : "medium" } , style : { fontSize : 16 } } , { props : { size : "large" } , style : { fontSize : 24 } } ] } } palette: { text: { primary: "#333" }, primary: { main: "#FFAD36", contrastText: "#fff" }, secondary: { main: "#2981C0", contrastText: "#fff" }, error: { main: "#D02F48", contrastText: "#fff" }, shadow: { main: SHADOW_COLOR }, disable: { main: "#757575" } } テストコード実装 テスト方針 ページごとに単体テストコードを実装しました。 アプリ内のあるページを実装した人が、テストコードも合わせて実装するという方針で進めました。 今回のテストコード実装では 主に画面に情報がきちんと表示されているか ユーザーの操作をテスト内で模擬し、正しい振る舞いが画面上でできているか といった、ユーザー目線に近い観点でテストコードを実装しました。 テストツール テストコード実装の際に使用したツールは以下の通りです。 Jest testing-library/react 気をつけたこと・学んだこと きちんとテストコードを書いたり、チーム内でテスト方針を考えたりすること自体初めてだったので、とても学びが多い部分だったと感じております。 APIとの通信が絡む処理については、サービスの共通モックをあらかじめ作ることで、テストコード実装者によって、API処理周りの処理の書き方にばらつきが出ないようにしました。 学んだこととしては、テストコード実装初心者だった我々にとって、Jestやtesting-library/reactで提供されている関数や概念を理解するのに苦労しました。この仕組みはどのように使われ、なぜ必要なのかを一つずつ学んでいきました。 実際に私たちが実装したテストコードの一例をお見せします。 test ( "slackにURLを送信するテスト" , async () => { renderDOM ( "register" ) const emailInput = screen . getByLabelText (/ メールアドレス /) const registerButton = screen . getByRole ( "button" , { name : / 登録 / }) // 入力 await userEvent . type ( emailInput , "test@example.com" ) // 登録実行 await userEvent . click ( registerButton ) await waitFor (() => { expect ( screen . getByText (/ まだ登録は完了していません! /)) . toBeInTheDocument () }) }) test ( "slackにURLを送信するエラー" , async () => { mockRejectedValueOnce ( userService , "sendPasswordRegisterNotification" ) renderDOM ( "register" ) const emailInput = screen . getByLabelText (/ メールアドレス /) const registerButton = screen . getByRole ( "button" , { name : / 登録 / }) await userEvent . type ( emailInput , "test@example.com" ) await userEvent . click ( registerButton ) await waitFor (() => { expect ( screen . getByText (/ 登録できませんでした。再度試してください。 /)) . toBeInTheDocument () }) }) こちらのコードはランチ相手が決定し、Slackに招待を送る部分の処理のテストコードです。正常系でテストを行った後に、異常系やレアケースもきちんとテストするようにしました。 何をテストし、どのような期待値が求められるのかをきちんとコード内で明文化することを意識しました。このコードに関して言うと、ユーザーのタイプ操作やクリック操作は非同期処理になるため、適切にawaitを仕込む必要性がありました。また、ユーザー操作の結果を受けて実行される処理に関しても、ユーザー操作の非同期処理を待ってから処理が走るように設定しなければなりませんでした。この設定を怠ると、非同期処理の部分だけ適切に実行されずに空回りし、望まないテスト結果につながってしまいます。 試行錯誤した部分 上述のように、フロントエンド開発といってもいろいろな工程があります。 特にこの中で一番試行錯誤した点を上げるとすれば、UI・UXデザインだと感じております。 「ユーザーが直感的に使えて、ストレスを感じさせないようなページ遷移、UIデザイン、誘導を行うにはどうすればいいのか」 このテーマを常に意識しながら画面定義を行っていたのですが、毎回スプリントレビューで上司の方々からご指摘をいただいておりました。 主にいただいていたご指摘としては、 同じ確認を何度もさせているので、ユーザーにとってストレス ボタンを押した後の結果が推測できない パーツの配置、サイズにどんなコンセプトがあるのかが考慮されていない などがありました。 改善例 トップ画面の改善 トップ画面からランダムマッチングを選択した際、もともとは以下のような画面レイアウトと遷移を定義していました。 isai-connect-画面遷移 こちらの画面遷移についてご説明します。まず、左側にあるトップ画面に「異才を探す」という黄色いボタンがあります。このボタンをクリックすることで、真ん中に表示されている画面に遷移します。左側にある「ランダムに異才をさがす」というボタンを押すことで、画像右側にある確認画面へと遷移します。 しかし、これでは「異才を探す」という選択を、ユーザーに3回もさせてしまうことになり、明らかに冗長な工程だとわかります。 そこで、操作回数を減らしつつ、ユーザーが操作内容を直感で推測できるように改良したものが、以下の画像になります。 isai-connect-マッチングの選択画面 マッチング方法の選択作業をページ一つにまとめ、ボタンを押せばどのような処理が走るのかが一目でわかるような見た目にしました。 また、上二つのボタンと下の「部署を選んで」ボタンにも工夫した点があります。上二つのボタンは、押下後にすぐにマッチングが走る仕組みです。人数は異なりますが、「すぐにマッチングが走る」というコンセプトが一致しているため、ボタンのサイズや形状を統一させています。対して、下の「部署を選んで」ボタンは、ボタン押下後に部署の選択画面に遷移するため、すぐにマッチングが走るというわけではありません。このようにコンセプトが上の二つのボタンとは異なるため、形状とサイズに違いを持たせました。 プログレスバーの追加 ユーザーが実際にマッチングを行い、マッチングが終わった後の結果表示からSlackへのメッセージ送信完了への画面遷移に関しても、改善前は以下のように定義していました。 isai-connect-プログレスバー実装前 「トップ画面の改善」の節でお見せした改善前の画面遷移と合わせて見てみます。私たち開発側からすれば、この遷移で問題ないように見えてしまいますが、この状態でリリースされたアプリを初めて触るユーザーは違います。この画面遷移のままだと、ユーザーが何かボタンを押した後の結果が、実際に遷移されてからでないとわかりません。 この問題を解決するために導入したのが、画面上部に表示するプログレスバーです。プログレスバーを導入することで、ある操作をした後にどういう状態になるのかが一目でわかるようになりました。 isai-connect-プログレスバー実装後 アプリのフロントエンド開発をしてみて 私たちは常日頃からアプリケーションを利用しています。しかし、いざユーザー視点でアプリを作るとなると、日ごろからアプリを利用している身であるにもかかわらず、考えが及ばなかったり、意思決定に時間がかかったりする場面がたくさんありました。フロントエンド開発に今後も携わる身として、ユーザーにストレスを与えず、直感で機能・場所・状態を即座に推測できるようなUI/UXデザインができるように、日々精進していきたいと思います。私たちが開発したアプリを通じて、部署の垣根を超えたコミュニケーションが、社内でより活発になれば幸いです。 最後までご覧いただきありがとうございました。
この記事は Safie Engineers' Blog! Advent Calendar 4日目の記事です。 はじめに  こんにちは。第3開発部でエンジニアをしている伊東です。今回は2024年新卒エンジニア研修における、バックエンド分野の開発についてお話しします。 はじめに セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介 機能 設計 ER図 シーケンス図 API仕様書 実装 使用技術 ディレクトリ構成 認証 工夫したポイント おわりに セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介  セーフィーでは全体研修後、約3ヶ月のエンジニア研修があり、エンジニアとしての基礎を学びつつ、社内課題を解決するプロダクトを開発しました。課題選定から開発言語、体制まで全て自分たちで決める形式で、私たちは「isai connect」を開発しました。 これは、他部署の方とランチに行くと会社がランチ代を負担してくれる「異才ランチ」制度をより活用し、活発化を図るためのプロダクトです。 詳しくは以下の記事でご覧ください。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド ←本記事 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 機能  isai connectで実装した主要機能は以下の通りです。isai connect管理者にはユーザ管理機能、ユーザにはレコメンド機能とコネクト機能を提供しています。 ユーザ管理 月一で従業員名簿からユーザを作成・更新・削除する機能です。isai connectに登録していないユーザもレコメンドし、コネクトさせる必要があるため従業員名簿からユーザを一括登録しています。 レコメンド 一緒にランチするメンバーを指定したチームからランダムに提示してくれる機能です。「全従業員が保持している属性」「属性判別が容易」の2観点からチーム指定をまず実装しました。今後は様々な属性を指定できるようにしていきます。 コネクト レコメンドされたメンバーに対してランチを誘うSlack DMを送信し、ランチ参加者だけのSlackグループを作成する機能です。社内の主なコミュニケーションツールであるSlackのDM機能を使用することで、メッセージの見逃しリスクを低減し、スムーズな意思決定を促進しています。 設計  isai connectのバックエンド実装を設計書を用いて説明します。 ER図  以下にisai connect用のER図を示します。  各テーブルが管理している情報は以下の通りです。 ユーザ情報 部門情報 部署情報 グループ情報 コネクト実績情報 ユーザに関連する製品情報 コネクト状況の情報 コネクト参加者の情報  コネクト実績とユーザに関連する製品情報を管理するテーブルは、ユーザと多対多の関係になることから、中間テーブルを使用して効率的に管理できるようにしています。 シーケンス図  開発当初は処理ロジックを明確にするためシーケンス図を作成していましたが、API仕様書をフロントエンドと協議して作成する際に実装ロジックが明確になり、共通理解が生まれるため、途中から作成されなくなりました。開発時のシーケンス図の利用頻度は高くはありませんでしたが、参考としてレコメンドからコネクトまでのシーケンス図を掲載します。 API仕様書  1〜3スプリントでは、認識の齟齬を生まないために予めフロントエンドと協議してAPI仕様を策定し、仕様書としてドキュメント化していました。4スプリント目では協議とドキュメント化の時間削減を目的に、バックエンドが定義したAPI仕様をSwagger UIを通じてフロントエンドに渡し、必要であれば都度修正するという試みを行いました。しかしながら、この試みは都度修正の特性上、フロントエンド・バックエンド双方に手戻りを多く強いることになり、結果として多大な時間を浪費することになりました。Swagger UIをドキュメントの代替として用いたことは間違ったアプローチではありませんでしたが、事前の十分な協議なしにこの方法を導入したことが問題でした。  以下に最終的に作成したisai connectのAPI仕様書の一部を示します。  isai/random/は、部門及び部署をクエリパラメータとして指定することで、ランダムなユーザを一人取得するためのAPIです。また、isai/connectは、ホストユーザーと1~3人のゲストユーザーのIDを受け取り、コネクトを行うためのAPIです。 実装  isai connectのバックエンドの実装方法について説明します。 使用技術  配属後の学習コストを減らす目的で、実際にセーフィーで活用されている言語及びフレームワークを選定しました。使用言語、フレームワーク、主要なライブラリは以下の通りです。 言語 Python フレームワーク FastAPI ライブラリ Pydantic SQLAlchemy Alembic ディレクトリ構成  バックエンドの1層目の主要ディレクトリ及びファイルを以下に示します。 app/ ├── connect_db.py ├── cruds/ ├── init_db.py ├── main.py ├── models/ ├── routers/ ├── schemas/ ├── tests/ ├── util/  このディレクトリ構成で開発を進めた結果、ビジネスロジックがcrudsに集中してしまい、ファイルの肥大化と難読化が進行、重複処理が増加するという課題が生じました。この課題を解決するため、今後の開発ではディレクトリ構成の見直しが必要だと考えています。改善策としては、crudsディレクトリ内では純粋なデータベース操作のみを許容し、ビジネスロジックは新たに作成するservicesディレクトリに移動、共通処理の抽出などが挙げられます。これらの変更により、コードの構造が明確になり、保守性と可読性の向上が期待できます。 認証  isai connectの認証では、セキュリティ面と実装の容易さから、OAuth2をベースに、JWTを認証トークンとする方式にしています。基本的な実装はFastAPI公式に従っていますが、トークンが漏洩した場合のリスクを軽減を目的に、追加でリフレッシュトークンを導入しています。また、トークンの保存にはHttpOnlyおよびSecure属性を持つCookieを使用しています。 工夫したポイント  isai connectでは、ユーザの情報取得やDM送信のためにSlack APIを使用しています。これを逐次処理でおこなうと相当な時間を要するため、処理を並行化やマルチスレッド化することが検討されました。並行化とマルチスレッド化を比較したところ、処理時間平均に大きな差異はありませんでしたが、最小と最大の処理時間の差が並行化した方が少く、安定した性能であったため、並行化が採用されました。結果として、DM送信の処理時間を逐次処理したときと比較して2.6倍ほど早めることができました。 おわりに  筆者は、インターンシップで個人でのシステム開発経験を積む機会がありましたが、チームとしてフロントエンドやバックエンドに分かれての開発経験は限られていました。個人開発の際には、設計のドキュメント化を軽視する傾向がありましたが、チーム開発を経験した現在では、チーム内共有の観点からその重要性を強く認識するようになりました。現在所属する部署では、実装だけでなく、事前の調査や事後の評価など、実装の前後のプロセスも成果物の一つとして重視されています。そのため、配属前にドキュメント化の重要性に対する意識を高められたことは、非常に有益でした。  今後、isai connectは開発フェーズから運用フェーズに移行しますが、システムの拡張を継続し、社内コミュニケーションを活発にしたいと考えています。  最後までご覧いただき、ありがとうございました。
この記事は Safie Engineers' Blog! Advent Calendar 3日目の記事です はじめに こんにちは、第1開発部でサーバーサイドエンジニアをしている坂上(さかうえ)です。今回は2024年新卒エンジニア研修で開発したプロダクト「isai connect」について、その背景や機能を紹介します。 はじめに isai connectを作った経緯 ターゲットと課題と解決策 開発を始める前のエピソード isai connectの機能紹介 異才ランチに誘う人を探す機能 SlackのグループDM自動作成機能 コネクト履歴機能 コネクト実績機能 デバイス isai connectを育てていく isai connectを作った経緯 2024年新卒エンジニア研修では、「社内課題を解決するプロダクト開発」というテーマについて取り組みました。これは、セーフィーの中で抱えている課題を見つけ、その解決に向けて、要件定義、設計、開発から運用まで一気通貫して取り組むというものです。 こうして生まれたのがisai connectです。以降では、本プロダクトの開発に着手するまでに行なったセーフィーの中で抱えている課題、ターゲットと解決策の模索、そして、それを解決するisai connectの機能について、紹介します。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて ←本記事 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 ターゲットと課題と解決策 まず、会社における「不」を探るため、以下のプロセスを行いました。 自身の体験や感じたことをリストアップし、共感度の高いものを優先 ユーザーインタビューを実施し、社員の生の声を収集 そこで浮かび上がったのが、セーフィーの福利厚生の1つである異才ランチの課題です。 異才ランチは、他部署の社員とのランチ代を月に2回支給される制度です。新たなアイデアの創出や他部署を巻き込むきっかけとなったり、相互理解に繋がったりといった目的があります。しかし、ユーザーインタビューを通して、以下のような意見がありました。 異才ランチを使えていますか? ついつい月滅になってしまって使えていない できれば知らない人と使いたいけれど誘えない 異才ランチでこうであったら良いなと思う点はありますか? 同じコミュニティ内(開発本部内で仲の良い人など)で固まってしまうことが多いので、他の人と行くきっかけが欲しい このように、社員の中には、誰を選んだら良いかがわからない場合や、見知った仲の方ばかりと行く場合などがあることがわかりました。これは、本来の目的に沿った異才ランチの活用に障壁を感じている方がいるということもできます。 そこで、ターゲットと課題、解決策を以下のように定めました。 ターゲット 異才ランチを使いたいが、誰を誘おうか迷っていたり、そこに障壁を感じている社員 課題 異才ランチを行う際、誰を誘うかやきっかけ作りが利用者にとって難しく感じられることがある 解決策 異才ランチを利用する社員をマッチングするシステムを構築し、異才ランチの実現をサポートする 開発を始める前のエピソード ターゲット、課題と解決策が定まったところで、いざ設計や開発が始まると思いきや、ここで社会人ならではの経験をしました。それは、セーフィーのCTOである森本さんから承認を得るプロセスです。 本プロダクトの開発には、会社の予算が必要でした。そのため、必要なコスト(備品やインフラ料金)を見積もり、プロダクトの価値やメリットを説明する資料を作成しました。7人の想いを込めて、森本さんに直接交渉を行いました。最終的に、無事に賛同していただき、開発のための予算を獲得することができました。この機会を通じて、技術的な関心に留まらず、プロダクトが持つ価値が実際にどのような影響を与えるのかを深く考える貴重な学びを得ることができました。これらを踏まえてisai connectはとても思い入れの深いプロダクトになっています。 isai connectの機能紹介 異才ランチに誘う人を探す機能 課題に挙げた異才ランチに誘うキッカケ作りをより簡単に、より気軽にするために、isai connectでは、3つの方法で異才ランチに誘う機能があります。 3人まとめて 他部署の社員の方々を3人ランダムに選ぶことができます。ボタン1クリックで、気軽に誘うことを目的にこの機能を作りました。 1人ずつ 1人ずつランダムに選ぶことができます。ユーザーが希望する人数でランチに行けるように、段階的に招待する人数を増やせるような機能を作りました。 部署を選んで 部署を選んで、その中から1人ずつランダムに選ぶことができる機能です。興味がある、または、知りたい部署があるユーザーのためにこの機能を作りました。 SlackのグループDM自動作成機能 異才ランチに誘う人を探したのちに、招待メッセージをSlackで送ることができます。異才ランチを始める最初のSlackメッセージを自動化することで、より気軽に誘えるきっかけにしました。また、誘われた人も気軽にお誘いに応えられるようにしました。 誘う人のSlack画面誘った人が、誰に誘ったのかを改めて確認できると同時に、Notion自己紹介のリンクを載せました。ランチに行く前にどんな人なのかを手軽に確認できるようになっています。 誘われた人のSlack画面 誘われた人が、行きましょう😀、か、ごめんなさい💦ボタンを押すことで、誘った人にランチに行けるかどうかのメッセージが飛ぶようになっています。 異才ランチが成立した場合のSlack画面異才ランチが成立すると、自動でグループDMが作成され、参加者に成立したことをお知らせするメッセージが送られます。また、ランチが終了した後に「いってきた」ボタンを押すと、経費申請の手順と必要な情報がSlackでまとめて確認できるようになります。 異才ランチが不成立だった場合のSlack画面誘われた人全員が異才ランチに行くことができなかった場合に、誘った人にその旨を通知する機能です。 コネクト履歴機能 今まで一緒に異才ランチに行ったメンバーや、現在進行中の異才ランチのお誘い状況を一覧で確認できる機能です。これによって、24新卒メンバーの自身の体験にあった、どの方といつ異才ランチに行ったのかが一目瞭然になっています。 コネクト実績機能 異才ランチを利用した回数や、一緒に行ったメンバーの部署の種類の数に応じて、実績を付与する機能です。これは、異才ランチの利用をより楽しくしてもらえるように作りました。この実績は今後増やして行く予定です。 デバイス Webアプリケーションだけでなく、専用デバイスも開発しました。このデバイスは、顔認証後にユーザーのSlackにレシート画像を直接送信する機能を備えています。これにより、異才ランチ後の経費申請で、スマホでレシートを撮影してPCに送る手間を省けます。ユーザーインタビューで挙がった「経費申請の手間を減らしたい」という要望に応える仕組みです。 さらに、デバイスの画面には、isai connectを通じて行われた異才ランチの月間利用数と累計利用数が表示されており、利用状況がリアルタイムで分かるようになっています。 isai connectを育てていく 様々なバックグラウンドを持った24新卒エンジニア7人が集まって、四苦八苦しながらこのisai connectを作りました。今後は、日々の業務と並行して、このプロダクトを運用するだけでなく、さらに成長させていきます。本記事は、プロダクトの説明に留まってしまいましたが、他の記事では、プロダクトを作る過程やチームの様子などを知ることができます。ぜひご覧ください。
この記事は Safie Engineers' Blog! Advent Calendar  2日目の記事です。 はじめに こんにちは!第1開発部でサーバーサイドエンジニアをしている古谷です! 今回は2024年新卒エンジニア研修で行った開発について開発体制の観点からお話しします! はじめに セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介 開発体制をアジャイル開発に決定 スクラムで意識したこと スクラムイベントでの工夫 ユーザーストーリーマッピング スプリントプランニング デイリースクラム スプリントレビュー レトロスペクティブ ポジティブ共有会 結果と感想 セーフィーでの2024年新卒エンジニア研修と作ったプロダクトの紹介 セーフィーでは全体研修後、約3ヶ月のエンジニア研修があり、エンジニアとしての基礎を学びつつ、社内課題を解決するプロダクトを開発しました。課題選定から開発言語、体制まで全て自分たちで決める形式で、私たちは「isai connect」を開発しました。これは、他部署の方とランチに行くと会社がランチ代を負担してくれる「異才ランチ」制度をより活用し、活発化を図るためのプロダクトです。詳しくは以下の記事でご覧ください。 新卒研修の紹介とチーム開発前にやったこと 2024年新卒エンジニア研修-アジャイル開発編 ←本記事 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 開発体制をアジャイル開発に決定 開発は新卒エンジニア7人で行いました。開発体制を決める上でウォーターフォール開発かアジャイル開発にするかをチームで話し合い、悩んだ結果アジャイル開発のスクラムに決めました。決め手は新卒研修の目的の一つに「プロダクトが成長し続けることの重要性を知る」が設定されていたことです。3ヶ月という短い期間でプロダクトを成長させるという経験を積むためには、アジャイル開発のスクラムを用いて小さくリリースを繰り返すことが目的の達成に繋がると考えました。 スクラムで意識したこと 私はチームでスクラムを行っていく上でスクラムマスターというポジションでチームに貢献していました。スクラムマスターはチームで誰よりもスクラムに詳しい必要があり、チームにスクラムを浸透させる役割があります。私が意識したことは「一般的な基本のスクラムを実践すること」です。スクラムはよくチームに合わせてアレンジして使われることが多く、正解はないとされています。しかしチームではまだ誰もスクラムを実践したことはなかったためアレンジをする前にまずは基本を知るということを大切にしました。そのために、Udemyを用いてスクラムを勉強しチームに普及しました。今回はスクラムマスターとプロダクトオーナー(テックリードの方が役割としては近い)を役割としてチームで定め、スプリントは2週間で開発を進めました。また、スクラムを実践する上でカンバンも取り入れました。 スクラムイベントでの工夫 ここからはアジャイル開発のスクラムを実践する上での工夫についてお話しします。 ユーザーストーリーマッピング スクラムではスプリントという短い期間(今回は2週間)での開発を繰り返し何度もリリースをすることでプロダクトを作り上げていきます。異才ランチをよくするプロダクトを作ると決めた後、課題を解決するための機能に優先順位をつけるために図1のようなユーザーストーリーマッピングを作成しました。ユーザーストーリーマッピングでは「誰が・何を・なぜ」という型で課題を一つ一つの付箋に書き出し、時系列を横軸に優先度を縦軸に並べたものを作成します。開発する機能の優先順位をつけるための付箋に書き出す課題のちょうどいい粒度を見つけることが難しく、2回作り直しました。「何を」の項目に対して「異才ランチで誰を誘っていいかわからないことを解決する」というような粒度から「異才ランチを誘う相手をランダムで提案する」という機能により近い粒度で書くことで解決しました。始めはこのユーザーストーリーマッピングは意味あるのだろうかと不安になることもありましたが、途中からはスプリントプランニングをしていく上でなくてはならない存在になっていました。 図1 ユーザーストーリーマッピング スプリントプランニング スプリントプランニングは各スプリントの最初に行い、その2週間で何を達成するためにどのコア機能を作るのかをユーザーストーリーマッピングを元にチームで話し合います。まず始めにスプリントゴールを決め、次にそのスプリントゴールに必要なコア機能を取捨選択します。そして、各タスクにストーリーポイントを割り振り、チームのベロシティと比較し調整して完了です。図2はスプリントプランニング後のユーザーストーリーマッピングの一部です。スクラムイベントの中でスプリントプランニングが一番大変でした。7人のイメージしているプロダクトのイメージがそれぞれバラバラで合わせるために長い時間を使ってしまいました。スプリントプランニングの時間はスプリントが1ヶ月だと8時間ぐらいという目安があります。(参考: スクラムガイド )今回はスプリントが2週間なので半分の4時間ぐらいまでだと考えていたのですが、人数が7人と多かったことと、スプリントプランニングを適当にすると変なものが出来上がり2週間の意味がなくなってしまうという理由で時間が伸びても気にしないようにしていました。長い時は7時間以上かかることもありました。とはいえ話し合いに時間をかけすぎて実装の時間が無くなっては困ります。7人のイメージを合わせることに一番時間を使っていたので、その工夫として7人で図3のような簡易的な画面遷移図を作成するなどをしていました。この工夫をするまではイメージを合わせるのに時間がかかっていたり、イメージがずれていることに気付けなかったりということが多かったのですが、この工夫により話し合いがスムーズに進むようになりました。 図2 スプリントプランニング中のMiro 図3 簡易的な画面遷移図 デイリースクラム 毎朝30分ほどバーンダウンチャートを確認しながらスプリントが順調に進んでいるか問題点はないかということを全員で共有する場を作っていました。各メンバーが問題点を早期に共有できるように雑談を織り交ぜたりチェックインをしたりしながら話しやすい環境作りを意識しました。また、スクラムマスターから問題があるか聞くのではなく各メンバーから報告する形にすることで自分から報告するという癖を付けられるようにしました。 スプリントレビュー スプリントレビューでは2週間で開発したものをお披露目し、レビューを受けることで目的を解決できるものになっているかなどを確認しました。第3回目のスプリントレビューからは初めてisai connectを知る方も交えて行うことでより利用者の気持ちを知ることができました。第3回のスプリントレビューではコア機能に触れるまでクリック数が多すぎるという指摘を受けました。動作に説明をつけたいという理由で増やしており、コア機能までは5クリックほど必要でした。しかし、コア機能まで遠いとその分だけユーザーが離脱してしまう可能性が高くなります。最後にはコア機能に触れるまで最短2クリックでできるようになりプロダクトを改善させることができました。他にもスプリントレビューで受けたフィードバックを抜粋しておきます。 ローディングはワクワク感を強めるために一部長めにしてもいいのでは?(第2回スプリントレビュー) ホーム画面の余白が気になる(第3回スプリントレビュー) 日程調整機能があると嬉しい(第3回スプリントレビュー) UIは良くなったがUXはまだまだ改善の余地がある、slackのユーザーに通知が送られた後にユーザーが混乱してしまいそう(第3回スプリントレビュー) シャチのローディングのUIが素晴らしい(第4回スプリントレビュー) 自分たちのロゴはどのページでも表示するようにしよう(第4回スプリントレビュー) レトロスペクティブ レトロスペクティブでは図4のKPT法を用いて「継続すること・改善したいこと・挑戦したいこと」に分けて2週間の振り返りを行いました。今後のチーム開発をより良くするにはということを話し合う上で振り返りとなるとつい改善点ばかりに目を向けてしまいがちですが、KPT法を用いることで良かった点にも目を向けることができ気持ちのいい振り返りをすることができました。ここで出たTryはデイリースクラムで毎日確認し、意識できるように心がけました。 図4 KPT法 ポジティブ共有会 スクラムイベントとしてあるわけではありませんが、ポジティブ共有会を実施しました。これはチームメンバーのいいところをそれぞれNotionでチケットに書き出し褒め合うというものです。きっかけはチームメンバーから自分がどう思われているのか知りたいという要望からでした。3ヶ月のチーム開発をしている間に2回実施しました。スクラムではチームワークがとても重要です。スクラムは全員が自律的に動くことが大切であり、バラバラに動いていては完成するプロダクトもいいものが作れなくなってしまいます。褒め合う時間は少し恥ずかしい面もありましたが、チームメンバーの相互理解の向上や自分がどこで期待されているのかなどを知ることができ、チームの一員としての帰属意識が強くなりました。 結果と感想 新卒研修ではスクラムを用いて5スプリント目まで回すことができました。1スプリント目で開発したものと5スプリント目で開発したものではクオリティが全く違い、プロダクトを成長させていくという経験も積むことができました。Safieというプロダクトを作っていく会社でエンジニアをしていく上で今後自分たちのプロダクトがどんなものになっていくのかを意識しながら開発しなければ世の中の課題を解決できないプロダクトになってしまいます。プロダクトが成長し続けることが重要というよりはプロダクトを運用していく上でどう成長させていくのか見失わないことが重要だと感じました。 この記事を読んで入社した時のイメージがついていただければ幸いです。他の記事では私たちの作ったisai connectについてなども書かれていますのでぜひご覧ください。
この記事は Safie Engineers' Blog! Advent Calendar 1日目の記事です 導入 はじめまして!2024年に入社し、現在は第4開発部 コアデバイスグループに所属しております山口と申します。 今回は、2024年新卒エンジニア研修の紹介をしたいと思います。 導入 新卒研修の軽い紹介 チーム紹介  開発体制について タスク管理について コーディング規則 チーム開発前にやったこと まとめ 新卒研修の軽い紹介 セーフィーの新卒研修ではチーム開発をおこなっています。チーム開発を通してチームマネジメント・開発(フロント、サーバ、デバイス、CI・CD)・運用・保守を学んでいくのが目的になります。 チーム開発では、2024年度の新卒エンジニアは7名で「社内の負」を解決するシステムについて、アイデアを出し合って開発しました。新卒エンジニアメンバーで話し合った結果、社内で用意されている「異才ランチ」という福利厚生制度に着目し、これを活性化しようという話になりました。 異才ランチを効率化するシステムについては他のテックブログで紹介があると思いますので、本記事では頭出し程度にしておきます。 また、新卒研修のトレーナーには去年の新卒の方々が担当してくださっています。 新卒エンジニアメンバーが執筆した記事については以下リンクをご参照ください! 新卒研修の紹介とチーム開発前にやったこと ←本記事 2024年新卒エンジニア研修-アジャイル開発編 2024年新卒エンジニア研修-isai connectについて 2024年新卒エンジニア研修-isai connect開発のアウトプット_サーバーサイド 2024年新卒エンジニア研修-フロントエンド開発編 2024年新卒エンジニア研修-インフラ構築編 2024年新卒エンジニア研修-isai connect開発のアウトプット_デバイス編 2024年新卒エンジニア研修-新卒研修の成果発表とその後 チーム紹介  新卒研修を始めるにあたってチーム名とロゴを作成しました。 今後会社で24卒エンジニアと認識されるより、チームとして認識してほしいこと、チームとして一体感をだしたいなどが理由です。 チーム名には悩みましたが、各メンバーの頭文字をとって SSSTIFY(トリスティフィー) と命名しました。 ロゴは新卒デザイナーに作成してもらいました。こちらも何度かやり取りを繰り返し、最終的に以下のようなデザインになりました。 このロゴは開発したプロダクトでも使用しています。 開発体制について チーム開発でプロダクトをリードするプロダクトリーダーと技術まわりについてリードするテックリードといった役割もチームで選出しました。 出社する頻度もチーム内で決め様々な働き方を試してみて、自分にはどの働き方があっているかなども考えました。 リモートワークでのコミュニケーション方法について考え、我々は「Gather」を使用してみました。リモートワークの際、Gatherではマップ上で話しかけたい人の近くに移動すれば会話ができるので、現実でちょっと話を聞きに行くみたいな感覚に近いものがあって気軽に相談もできたのが良かったと思います。 下記の画像は実際に開発中に使用したGatherの画像になります。 利用ルールは下記のように定めていました。 利用ルール 利用する曜日:月・水・木 リモート可能な日に利用 リモート: 月 出社: 火・金 自由: 水・木 ログイン時間:出勤時~退勤時まで 利用範囲はSSSTIFYの7名とする 他者の招待は原則NG トレーナーさんなど参加が必要な場合は相談 カメラは常にON マイクは基本OFF、話しかける際にON 雑音などを拾う可能性があるため 全体のミーティングはGoogle Meetを利用 個人間の話し合いではGather上で画面共有など利用可 チャットはSlackを利用し、Gatherでは使わない 履歴を残しておくため 我々のチーム開発ではアジャイル開発を取り入れ、スプリントごとに各領域の開発をできるようにもしました。 例えば、今スプリントでフロント開発していた人が次回スプリントではデバイスの開発をしているといった感じです。 下記のように開発に向けたチームの目標を決めていました。 チームでやりたいこと(目標) 型にはめたスクラムを経験してみる レビューを定期的に受ける(進捗定例) スプリントを2周以上回す経験をする 使ってもらえるプロダクトにする 満足度などの定量指標を設ける 自分が使いたいプロダクトを作る 全員が全ての技術に触れる 1スプリントで二つ以上の技術を触れることが目標 いっぱいタスクを消化している人にどんなタスクあるか相談するのもあり チケットは基本メインの技術スタックをとっても良いが、他の分野についてはレビューで寄与する レビューでは動いたからOKといったものではなく、積極的に実装者やその他の関係者に質問するなど理解を深めるよう意識する タスク管理について タスク管理には「Backlog」を使用しました。 チーム開発ではgitのbranch名をBacklogの課題IDと紐づけることでgithub上でプルリクがマージされたらBacklog上でのチケットも完了ステータスに移行するようにし、なるべく繰り返し作業を減らすことを意識して開発に集中しやすい環境を目指しました。 githubのプルリクとBacklogのチケットが紐づいていることで、どのチケットに対するプルリクなのかも一目で判断でき履歴を追いやすいのも良かったです。 コーディング規則 こちらはプルリクでチェックすべきコーディング規則の内容になります。プルリクの内容がどの領域に対する変更なのかでプルリクのテンプレートが決まります。 テンプレに記載されている項目を担当者は確認し、確認ができし次第レビューを各メンバーへお願いするといった流れでレビューしていました。 また、各フォルダごとにCodeOwnerを選定し、各領域で必ずレビューをもらうべき人を決めていました。変更に対して責任を持ってレビューをするといった意味合いも含んでいます。 チーム開発前にやったこと チーム開発がはじまるまでは初めに「Udemy」という動画教材を取り扱っているサイトを用いてプログラミングの基礎について学習をしていました。 私たちはセーフィーの新卒二期生ですが、一期生の方々の研修フィードバックを元に厳選した動画を中心に学習を進めていました。 既に基礎を理解している人は自由に興味のある領域の動画で学習していました。動画教材のリストとしては、セーフィーのプロダクトに関係のあるものが主に選定されています。 Githubを学んだことがない人はUdemyの動画から学んでいましたが、それだけでは理解は浅かったので、チームで集まって実際にコマンドを叩きながら理解を深めていました。やはり実際に手を動かして学ぶのが一番です。 まとめ 本記事を読んで、少しでも入社したときのイメージがついていただけたら幸いです。 研修終了後、別々の部署に配属された事でSSSTIFYのメンバーとの直接的な接点は減りましたが、他部署のシステムや技術について気軽に相談できる関係性が構築できて今も仲良くさせてもらっています。 また、セーフィーでは新卒採用が始まってから2年が経過していますが、実際に研修を経験した方々のフィードバックから研修内容を柔軟に変えて、最良の研修を模索している段階にあります。 来年度入社される新卒エンジニアの方々のためにも、今回の研修についてしっかりフィードバックを行いました。 今後もさらに研修内容をブラッシュアップしていき、来年度の新卒エンジニアの方々が楽しいと思える研修にできると嬉しいです。