ジュニアエンジニアが要件定義について体系的に学んで得たこと

こんにちは。株式会社アップガレージグループ ITソリューション事業部のエンジニア加藤です。 最近、弊社の20%ルール内で取り組んでいる「要件定義の体系的な学習」について記事で共有します。

20%ルールとは?

弊社では、業務時間の20%を将来的なチャンスにつながるプロジェクトの探索やスキル習得の時間として使用可能です。 1日の業務稼働時間を8時間とすると、1週間で最大8時間がこれに割り当てられます。

経緯

これまで「要件定義」といった形式で体系立って学習したことが無く、自分の知識の最適化や棚卸しをしたかったため。

学習に使用した本は以下となります。

gihyo.jp

特に学びになったこと

備忘録程度に、それぞれの章ごとに特に学びになったことを記載していきます。

1章 要件定義の基礎知識

段取り8割

  • 開発規模、希望納期を把握する
    • 衝突・トラブルが発生しそうな箇所は予めルールを決めておく

潜在要求の明示化

  • 表明あり、認識あり(顕在要求):網羅的に整理し、より正確に理解する
  • 表明なし、認識あり(暗黙の要求):念を入れて確認を怠らない
  • 表明なし、認識なし(潜在要求):相手からの発信を触発させるよう、複数の角度から質問する
    • ※個人の解釈ですが「あ~そういえば◯◯◯ってよく使うかも!」のような回答が引き出せると良いイメージ

ウォークスルー

  • 意思決定者に全体を流れで理解してもらい、スムーズに承認が得られるよう準備・リハーサルを行う。

ベンダーとしてユーザーをリードする

  • あくまで指針を導き、それを正確に伝えることに徹する
  • ユーザーを置き去りにしたり、強制的に操作すべきでない

2章 要件定義の下調べ・段取りフェーズ

開発開始後の想定外を減らすこと

  • このフェーズでは想定外を減らすことに注力したい
    • 時間は有限なので可能な限り時間を節約する(資料作成など)
  • 把握した内容はユーザー(依頼側)にも共有し、お互いの認識齟齬を減らす

ユーザー側のステークホルダを可視化する

  • ユーザー側にとっての利害関係者・それとのやりとりを理解する

クリティカルパスは押さえる

ステークホルダの識別

  • 本当の顧客、ユーザーを意識する
    • 直接対面するユーザー側担当者の意見のみを鵜呑みにしない
    • 外部のステークホルダの存在に気づかず考慮漏れになりがち

3章 業務要求の分析・定義フェーズ

現行業務の調査

  • 現状の業務を調査し、理想とのギャップを明示化する
    • ここでもユーザー側の潜在要求を掘り出す
  • 各業務を一覧化し、自動化できる作業が無いか洗い出す

当事者(ユーザー)に「組織に不足する仕組みやスキル」を特定してもらうよう、ベンダとしてアプローチする

  • ここで不足している事項が業務要求となる

ビジネス・ルール分析の位置づけ

  • 暗黙的な業務やルールが存在すると開発の漏れとなり、結果的に使えないシステムになる
    • 開発に際して、ベンダ側一元的にでルールを作成→浸透できるようアプローチする

ステークホルダが主体のレビュー会を開催する

  • ベンダー側(要件定義担当)がファシリテーションを行う
  • 目的:要件を文書化した後、実際の事業における目標実現に対し有効か確認する

4章 機能要求の分析・定義フェーズ

必要不可欠なもの、必須でないものを判断する

  • 肥大化を防ぐため、不要なものは可能な限り整理・廃止する方向で進める

既存システムにおける不具合は、業務の仕組みから正しい形にする

  • システムの側面から追求すると期待値がズレることがある
  • 分析中に取り決めたこと、設計に引き続くことは文書化する

帳票

  • ビジネスやオンライン上の業務フローだけでなく、帳票等の集計結果も分析対象
    • 帳票出力では、「目的」「場所」「時間」「ユーザー」をきちんと把握する
    • 例:電子表示だけで良い場合、pdf化が必要な場合、紙として印刷機能が欲しい場合など
  • 一度に出力される量も踏まえ、期待される出力スピードを意識する

通常期と繁忙期のトランザクション量の違いも念頭に置く

外部接続

  • 連携箇所は漏れなく洗い出す
    • 何のために使用しているか、導入時に一覧を作る
  • システム本体に無いロジックを有しているため、開発・保守で考慮漏れになりやすい

5章 非機能要求の分析・定義フェーズ

性能

  • 応答の速さは「5秒以内」など定量的に定義すべきだが、通常のユーザーにはIT知識が無く定義は難しい
    • 仮に定量的に定義できても、通常の場合、要件定義時点で実現可能かは判断できない
    • 基本的に「コストとのバランス(コスパ)」で実施すべきか検討する

可用性

  • 顧客からの信用失墜リスクと、冗長化や高スペック化に必要なコストを天秤に掛ける
    • 基本的に「コストとのバランス(コスパ)」で実施すべきか検討する

保守性

  • 運用・保守は自動化することがよく目標になるが、人の入り込む余地は残しておいた方が良い?
    • 自動化しすぎてログを閲覧できない・ログが多すぎて管理できないケースも有り得る

移行性

  • 新システム導入時、確実に成功させる必要があるイベント
    • 以降前後の規模を見積もり、対策に過不足を作らない

セキュリティ

  • 前提として、セキュリティ対策と性能はトレードオフ
    • 事故による社会的信頼・経済的な損失を見積もった上で、システムに求められる強度を見誤らない

システム環境・エコロジー

  • 近年だとシステムにエコロジーを求めるケースも存在する(温室効果ガスやエネルギー効率など)
  • システムに必要なツールの物理的な制約

6章 要件定義の合意と承認・維持フェーズ

要件に関する討論と合意

意思決定の記録、経緯と理由の記録

  • 承認が得られなかった箇所は、強引な説明は避けて積み残しとして切り離す
  • 一部の問題点のために全体が覆されたり、作業が一時停止することは避けなければならない

討論→合意のサイクルはルーティン化する

  • 一定周期で合意形成する仕組みで、各メンバーの当事者意識を強める
  • 小分けに議論する

ライフサイクル管理

  • 正常稼働だけでなく「要求」が意図通り実現できているか?という観点で考える
    • 逆に要求されていない機能、例えば「開始当初実装されていたが、環境変化により不要になった機能」は、正常稼働の必要性は低い

自分自身に活かせたこと

学習終了後まもないため実際に要件定義書を書いておりませんが、実務において役立ったことは多数あります。

開発時に意識すべきことの順序を明示化できる

  • 要件漏れの防止や業務上の非効率の排除に寄与しました。

自社の価値にまた一つ気づいた

  • 直接役に立ったことではございませんが、開発者と実際の現場(店舗)が近く情報伝達が早い弊社は、ビジネスを進めていく上の障壁が一つ少なくメリットが大きいと感じました。

これからについて

要件定義に限らず開発に必要なスキルを継続的に学習し、品質向上や自分のスキルアップに繋げていきます。

株式会社アップガレージグループ ITソリューション事業部では、共に勉強し合い、成長し合うメンバーを大募集しています! www.upgarage-g.co.jp

開発時によく使う便利コマンドまとめ

株式会社アップガレージグループ ITソリューション事業部でCrooooberをはじめとしたWebサイトの開発・運用をしています、加藤と申します。
今回は、私が開発時によく使う便利コマンドを、備忘録としてブログに残しておこうと思います。

Ubuntu

Ubuntuのデスクトップ環境で開発を行なっているのですが、稀に日本語入力が効かなくなることがあります。
何をやっても解決しない時は際は下記コマンドでibusデーモンを強制的に再起動しています。

ibus-daemon -r -x

Git

ショートカットコマンド

ターミナル上でGitの操作を行なっており、入力の手間に煩わしさを感じたため下記コマンドでショートカットを作ってます。

git config --global alias.co checkout
git config --global alias.b  branch
git config --global alias.cm commit
git config --global alias.mg merge
git config --global alias.st status
git config --global alias.ps push
git config --global alias.pl pull
git config --global alias.rb rebase

使用例

# チェックアウト
git co {branch name}

# ブランチ表示
git b

# コミット
git cm -m "commit name"

# マージ
git mg {branch name}

# ファイルステータス確認
git st

# プッシュ
git ps origin {branch name}

# プル
git pl

# リベース
git rb {branch name}

grep

xargsと組み合わせて一括置換を行なうコマンドです。
大抵エディタの一括置換機能で十分ですが、修正対象が多い場合は時間が掛かるためコマンドを使用することもあります。

git grep -l "置換前" | xargs sed -i "s/置換前/置換後/g"

grep」コマンドではダメなの?という意見もあると思いますが、「git grep」はgitプロジェクト配下のみを探索するためこちらの方が高速です。

AWS S3にREST APIのみで画像アップロードをしてみた

株式会社アップガレージグループ ITソリューション事業部でCrooooberをはじめとしたWebサイトの開発・運用をしています、加藤と申します。

今回は、AWS S3にREST APIのみで画像アップロードをする機能を実現してみたので、記事として公開いたします。

 

 

背景

先日、AWSからこのようなお達しがありました。

2023年6月28日までにすべての AWS リージョンで、すべての AWS APITLS バージョン 1.0 と 1.1 が使用できなくなることを意味します。

aws.amazon.com

弊社にもSDKを用いてS3にファイルアップロードしているSpringアプリがあり、TLSv1.2に対応していない古いバージョンだったためアップグレードを試みたものの、使用しているフレームワークの関係で、期限内でのSDKアップグレードは現実的に困難という判断に至りました。

他の手段を探してみると、S3にはSDKに頼らずRESTのみで完結するAPIが提供されていたため、こちらを使用してアップロード機能を再実装する運びになりました。

PutObject - Amazon Simple Storage Service

 

実装

ファイル内のデータチェックや例外処理は省略しています。

public class S3Util {
/** * S3のRESTでコンテンツをアップロードする */ public static void upload(String s3BucketName, String, s3RegionName, String key, InputStream inputStream) { // ソースコードに秘匿情報を保持するのはセキュリティ的に問題なので、properties等に適宜格納する String awsAccessKey = "アクセスキー"; String awsSecretKey = "セキュリティキー"; URL requestUrl = new URL("https://s3-" + s3RegionName + ".amazonaws.com/" + s3BucketName + "/" + key); // inputStreamをバイナリデータとしてbyte配列に格納する byte[] fileBinaryData = IOUtils.toByteArray(inputStream); // バイナリデータ化されたコンテンツをハッシュ化する byte[] contentHash = AWS4SignerBase.hash(fileBinaryData); String contentHashString = BinaryUtils.toHex(contentHash); Map<String, String> headers = new HashMap<String, String>(); headers.put("x-amz-content-sha256", contentHashString); headers.put("content-length", "" + String.valueOf(fileBinaryData.length)); // S3のストレージクラスも選択可能。今回は頻繁にアクセスするデータのため「STANDARD」を選択 headers.put("x-amz-storage-class", "STANDARD"); // コンテンツのアクセス制限。「public-read」に設定 headers.put("x-amz-acl", "public-read"); // PUTとしてリクエストを準備 AWS4SignerForAuthorizationHeader signer = new AWS4SignerForAuthorizationHeader(requestUrl, "PUT", "s3", s3RegionName); // アクセスキー、シークレットキーを含めて認証情報を生成 String authorization = signer.computeSignature(headers, null, contentHashString, awsAccessKey, awsSecretKey); headers.put("Authorization", authorization); // HttpUtilsにてリクエスト実行 HttpUtils.invokeHttpRequest(requestUrl, "PUT", headers, fileBinaryData); } }

 

public class BinaryUtils {
	/**
	 * バイト配列を16進数でエンコードされた文字列に変換するメソッド
	 */ 
	public static String toHex(byte[] data) {
		StringBuilder sb = new StringBuilder(data.length * 2);
		for (int i = 0; i < data.length; i++) {
			String hex = Integer.toHexString(data[i]);
			if (hex.length() == 1) {
				sb.append("0");
			} else if (hex.length() == 8) {
				hex = hex.substring(6);
			}
			sb.append(hex);
		}
		return sb.toString().toLowerCase(Locale.getDefault());
	}
}

 

public abstract class AWS4SignerBase {
	// 使用する暗号化方式や日時フォーマットなど
	private static final String MAC_ALGORITHM_TYPE = "HmacSHA256";
	private static final String MESSAGE_DIGEST_ALGORITHM_TYPE = "SHA-256";
	private static final String ISO8601BasicFormat = "yyyyMMdd'T'HHmmss'Z'";
	private static final String DateStringFormat = "yyyyMMdd";

	protected URL endpointUrl;
	protected String httpMethod;
	protected String serviceName;
	protected String regionName;

	protected final SimpleDateFormat dateTimeFormat;
	protected final SimpleDateFormat dateStampFormat;

	/**
	 * aws signature v4の共通署名生成用のメソッド
	 */
	public AWS4SignerBase(URL endpointUrl, String httpMethod, String serviceName, String regionName) {
		this.endpointUrl = endpointUrl;
		this.httpMethod = httpMethod;
		this.serviceName = serviceName;
		this.regionName = regionName;

		dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat);
		dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
		dateStampFormat = new SimpleDateFormat(DateStringFormat);
		dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
	}

	protected static String getCanonicalizeHeaderNames(Map<String, String> headers) {
		List<String> sortedHeaders = new ArrayList<String>();
		sortedHeaders.addAll(headers.keySet());
		Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);

		StringBuilder buffer = new StringBuilder();
		for (String header : sortedHeaders) {
			if (buffer.length() > 0) buffer.append(";");
			buffer.append(header.toLowerCase());
		}

		return buffer.toString();
	}

	/**
	 * 署名に付与するヘッダー内容の正規化とソート
	 */
	protected static String getCanonicalizedHeaderString(Map<String, String> headers) {
		if (headers == null || headers.isEmpty()) {
			return "";
		}

		List<String> sortedHeaders = new ArrayList<String>();
		sortedHeaders.addAll(headers.keySet());
		Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);

		StringBuilder buffer = new StringBuilder();
		for (String key : sortedHeaders) {
			buffer.append(key.toLowerCase().replaceAll("\\s+", " ") + ":" + headers.get(key).replaceAll("\\s+", " "));
			buffer.append("\n");
		}

		return buffer.toString();
	}

	/**
	 * 署名に付与する内容の正規化
	 */
	protected static String getCanonicalRequest(URL endpoint, String httpMethod, String queryParameters, String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) {
		String canonicalRequest =
				httpMethod + "\n" +
				getCanonicalizedResourcePath(endpoint) + "\n" +
				queryParameters + "\n" +
				canonicalizedHeaders + "\n" +
				canonicalizedHeaderNames + "\n" +
				bodyHash;
		return canonicalRequest;
	}

	protected static String getCanonicalizedResourcePath(URL endpoint) {
		if (endpoint == null) {
			return "/";
		}
		String path = endpoint.getPath();
		if (path == null || path.isEmpty()) {
			return "/";
		}

		String encodedPath = HttpUtils.urlEncode(path, true);
		if (encodedPath.startsWith("/")) {
			return encodedPath;
		} else {
			return "/".concat(encodedPath);
		}
	}

	/**
	 * パラメータマップの正規化
	 */
	public static String getCanonicalizedQueryString(Map<String, String> parameters) {
		if (parameters == null || parameters.isEmpty()) {
			return "";
		}

		SortedMap<String, String> sorted = new TreeMap<String, String>();

		Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
		while (pairs.hasNext()) {
			Map.Entry<String, String> pair = pairs.next();
			String key = pair.getKey();
			String value = pair.getValue();
			sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false));
		}

		StringBuilder builder = new StringBuilder();
		pairs = sorted.entrySet().iterator();
		while (pairs.hasNext()) {
			Map.Entry<String, String> pair = pairs.next();
			builder.append(pair.getKey());
			builder.append("=");
			builder.append(pair.getValue());
			if (pairs.hasNext()) {
				builder.append("&");
			}
		}

		return builder.toString();
	}

	/**
	 * 署名の生成
	 */	
	protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope, String canonicalRequest) {
		String stringToSign =
				scheme + "-" + algorithm + "\n" +
				dateTime + "\n" +
				scope + "\n" +
				BinaryUtils.toHex(hash(canonicalRequest));
		return stringToSign;
	}

	/**
	 * Stringデータを元にハッシュ化
	 */
	public static byte[] hash(String text) {
		MessageDigest digest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_TYPE);
		return digest.digest(text.getBytes(StandardCharsets.UTF_8));
	}

	/**
	 * Byteデータを元にハッシュ化
	 */
	public static byte[] hash(byte[] data) {
		MessageDigest md = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM_TYPE);
		md.update(data);
		return md.digest();
	}

	/**
	 * 指定された文字列をHMACアルゴリズムで暗号化
	 */
	protected static byte[] sign(String stringData, byte[] key) {
		byte[] data = stringData.getBytes("UTF-8");
		Mac mac = Mac.getInstance(MAC_ALGORITHM_TYPE);
		mac.init(new SecretKeySpec(key, MAC_ALGORITHM_TYPE));
		return mac.doFinal(data);
	}
}

 

public class AWS4SignerForAuthorizationHeader extends AWS4SignerBase {

	private static final String ALGORITHM = "HMAC-SHA256";
	private static final String SCHEME = "AWS4";
	private static final String TERMINATOR = "aws4_request";

	public AWS4SignerForAuthorizationHeader(URL endpointUrl, String httpMethod, String serviceName, String regionName) {
		super(endpointUrl, httpMethod, serviceName, regionName);
	}

	/**
	 * 署名生成処理の呼び出し元
	 */
	public String computeSignature(Map<String, String> headers, Map<String, String> queryParameters, String bodyHash, String awsAccessKey, String awsSecretKey) {
		Date now = new Date();
		String dateTimeStamp = dateTimeFormat.format(now);

		headers.put("x-amz-date", dateTimeStamp);
	
		String hostHeader = endpointUrl.getHost();
		int port = endpointUrl.getPort();
		if (port > -1) {
		    hostHeader.concat(":" + Integer.toString(port));
		}
		headers.put("Host", hostHeader);

		String canonicalizedHeaderNames = getCanonicalizeHeaderNames(headers);
		String canonicalizedHeaders = getCanonicalizedHeaderString(headers);
		String canonicalizedQueryParameters = getCanonicalizedQueryString(queryParameters);
		String canonicalRequest = getCanonicalRequest(endpointUrl, httpMethod, canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders, bodyHash);
	
		String dateStamp = dateStampFormat.format(now);
		String scope =  dateStamp + "/" + regionName + "/" + serviceName + "/" + TERMINATOR;
		String stringToSign = getStringToSign(SCHEME, ALGORITHM, dateTimeStamp, scope, canonicalRequest);

		byte[] kSecret = (SCHEME + awsSecretKey).getBytes();
		byte[] kDate = sign(dateStamp, kSecret);
		byte[] kRegion = sign(regionName, kDate);
		byte[] kService = sign(serviceName, kRegion);
		byte[] kSigning = sign(TERMINATOR, kService);
		byte[] signature = sign(stringToSign, kSigning);
	
		String credentialsAuthorizationHeader = "Credential=" + awsAccessKey + "/" + scope;
		String signedHeadersAuthorizationHeader = "SignedHeaders=" + canonicalizedHeaderNames;
		String signatureAuthorizationHeader = "Signature=" + BinaryUtils.toHex(signature);

		String authorizationHeader = SCHEME + "-" + ALGORITHM + " "
				+ credentialsAuthorizationHeader + ", "
				+ signedHeadersAuthorizationHeader + ", "
				+ signatureAuthorizationHeader;

		return authorizationHeader;
	}
}
参考

docs.aws.amazon.com

docs.aws.amazon.com

 

わかったこと

今回は苦肉の策で自前で実装しましたが、認証周りは複雑なためAWSからもSDKの使用が推奨されております。

フレームワークやライブラリはこまめにバージョンアップを行なうよう、仕組みづくりをしていきます。

 

おまけ

S3のプロトコル制限

開発期間中はまだAWS側でTLSv1.2の制限が掛かっていなかったため、バケットに以下のポリシーを設定して意図的に制限を掛けておりました。

s3:TlsVersionの箇所を変更すれば、任意のプロトコルに制限を掛けることが可能です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
"Action": "s3:GetObject", "Principal": "*", "Resource": "arn:aws:s3:::{S3のバケット名}/*", "Condition": { "StringNotEquals": { "s3:TlsVersion": "1.2" } } } ] }
参考

docs.aws.amazon.com

 

ポリシーが適用されてされているかの確認

SSLContextなどで任意の通信プロトコルを指定する方法も検討しましたが、サクっと確認したかったのでFireFoxの設定を変更して確認することにしました。

なお、通信プロトコル変更した状態での通信はデータ漏洩に繋がる危険がございますので、使用の際は自己責任でお願いいたします。

 

事前設定

1. アドレスバーに「about:config」と入力、Enter

2. 危険を承知の上で使用しましょう

3. アドレスバーに「security.tls.version」と入力

4. 「security.tls.version.max」と「security.tls.version.min」を任意の値にする

本来はブラウザ側が最適なプロトコルを選択して接続してくれますが、これを固定化して検証ツールとして用いることも可能です。なお、各値とプロトコルの紐付けは以下となっております。

数値 プロトコル
1 TLS 1.0
2 TLS 1.1
3 TLS 1.2
4 TLS 1.3

 

検証

非対応プロトコルでの接続が制限されることを確認

 

対応プロトコルでの接続に成功することを確認

 

作業終了後は元の値(2023年現在で「max: 4」「min: 3」)に戻しておきましょう。

偽ECサイトの存在、その対策について

株式会社アップガレージグループ ITソリューション事業部でCrooooberをはじめとしたWebサイトの開発・運用をしています、加藤と申します。

今回は、最近よく検索結果で偽物ECサイトを見るケースが増えたため、注意喚起も兼ねて記事を公開いたします。

 

はじめに

Google検索で表示される検索結果で、このようなサイトをご覧になったことは無いでしょうか?

 

2023年5月現在、これらのような通常のECサイトのような見た目で、顧客の個人情報取得や詐欺を狙っているWebサイトが蔓延しております。

 

消費者庁 - インターネット通販トラブル

 

ECサイトに限らず、2022年10月には「えきねっと」の偽サイトがGoogle検索トップに表示された事態が記憶に新しいと思います。

www.yomiuri.co.jp

 

偽EC西見分け方

本来の価格を大きく下回る価格での販売

  • 「95%OFF」といった、原価無視の通常有り得ない値引き率で商品を掲載している

URL、ドメイン

  • 日本のサイトにも関わらず、「fr」「cn」「de」など海外のTLDが設定されている

不自然なタイトル

  • 例えば自動車のホイール販売ページなのに、「出産祝い」「美容」など、通常結びつかない不自然なキーワードがタイトルに記載されている(SEO的に強いキーワードを過剰に用いている)

規約面の不正

  • 会社概要
    • 記載されている会社名が存在しない
    • メールアドレスが法人用ではなく、gmailhotmailなどが記載されている
  • 利用規約特定商取引法に関する表記
    • そもそも記載が無いなど

弊社ECサイトにおける影響について

弊社ECサイトであるCrooooberでも、画像と商品情報を不正に取得、無断転載されるケースが発生しておりました。

上記サイトは弊社側で対策済みで、その後も発生ベースで対策を行なっておりますが、新しいサイトが出現したりとイタチごっこであるのが現状です。

対策について

  • 「見分け方」にあるような不審なサイトは利用しない
  • 初めてアクセスしたサイトの場合、運営母体の企業に信頼性があるかを確認する
  • セキュリティソフトやファイアウォールの有効化、アプリのバージョンを最新に保つ

そもそも検索結果や広告側の審査を厳しくして欲しいというのが個人の意見ですが、それが実現されるにはGoogle側のSEO改善や総務省側の指導を待つ必要がありそうです...

被害に合わないためにも、まずは個人単位での注意をお願いいたします。

DDD勉強会

株式会社クルーバー ZERO TO ONE事業部アップガレージの基幹システムを開発・運用をしています、キムです。

 

まだまだ暑い日が続いていますが、みなさんいかがお過ごしでしょうか。

 

今回の記事は、少し前に社内で行われた勉強会について紹介したいと思います。

弊社について少しでも知っていただけたら幸いです。

 

はじめに

ZERO TO ONEでは、様々な読書会や勉強会を実施しています。

過去に行われた読書会については下記の記事を御覧ください。

今回は、もっとラフな形で有志を集めて行われたDDD勉強会について紹介していきます。

 

DDDとは?

ドメイン駆動設計(Domain-Driven Design)のことで、略してDDDです。

ざっくり説明すると、ビジネスの様々な問題を解決するために、必要なビジネス知識に焦点を当てた設計手法のことです。

詳しくはこちらの記事がわかりやすいかと思いますので、興味ある方は是非ご一読ください。

codezine.jp

 

DDD勉強会

添付画像のように、少し前にSlackで有志を集めて勉強会を実施しました。

返信数が98件も!(笑)

 

勉強会の内容としては、下記ページの概要に載っているYoutubeアーカイブをみんなで視聴しながら、気づきや感想などをSlackでコメントし合うという内容です。

アーカイブはまだ残っていますので、興味ある方は是非視聴してみてください!

ddd-community-jp.connpass.com

 

勉強会のルールに関しては、ラフでとてもシンプルです。

 

Slackでのやり取りの頻度は非常に高く、勉強会は大盛りあがりでした!

一部やり取りを抜粋して貼っておきます。

 

最後に

私自身、このような有志による勉強会への参加は初めてで、すごく新鮮で楽しかったです!

今後、私からも何かテーマを見つけた際には勉強会を主催してみたいと思いました。

 

ZERO TO ONEでは共に勉強し合い、成長し合うメンバーを大募集しています!

採用情報 | 株式会社クルーバー ZERO TO ONE事業部

20%ルールについて

株式会社クルーバー ZERO TO ONE事業部でCrooooberをはじめとしたWebサイトの開発・運用をしています、加藤(2020年度 新卒入社)と申します。

今回の記事では、弊社の制度である20%ルールについて、私がどのように活用しているのかを紹介したいと思います。

 


はじめに

弊社では2021年度下期から20%ルールを導入し、多くのメンバーがこの制度を利用しています。

20%ルールとは?

「主な業務とは別に20%の時間を自主的なプロジェクトに充てるべし」というルールのことです。

個々のスキルアップを目指し、最終的に組織全体の底力を上げることを目的としています。Googleが導入していた制度として有名ですね。

対象者

弊社の社員(1年目の新卒を除く)

具体的に

まず自分自身で伸ばしたい部分に対してテーマを決め、学習を進めます。

弊社では、基本情報技術者試験・要件定義/設計・インフラの勉強など人によってテーマが様々です。

20%ルールなので、時間は週当たり8時間割り当てられます。「毎日2時間 」や「金曜日のみ8時間」など実施タイミングは個人の自由です。

また、毎週火曜日にはアウトプットの時間を設けており、自分の発表が出来たり、他メンバーの実施項目について学ぶことが出来ます。

 


私のテーマについて

私の選択したテーマは「インフラ」です。

ZERO TO ONEが開発に携わっているプロダクトの多くは、リリース作業が自動化されており、画面操作だけでソースコードを本番環境にデプロイすることが可能です。

これにより、メンバー全員が簡単かつ安全にリリース作業可能になりました....が、自動化された部分において学ぶ機会が減ったとも言えます。主にインフラ系です。

それらの知識なしに開発が進められるのは素晴らしいことです。しかし、大きな機能を実装する際にインフラの観点から工夫したり、システムに障害が発生した場合の解決においてはまだまだ必要な知識とも言えるでしょう。

同時に「システムにおける全てのライフサイクルを触れるようになりたい」と考えている私にとって、早く身につけたい知識の一つでもありました。

(もともと自作PC自宅サーバーをイジるのが趣味でしたが、個人の趣味レベルでは業務に活かすことが難しいこともあり、行き詰まっていたところでした)

 

上記のような理由からインフラというテーマを選択しました。

こういった挑戦にはチェックポイントが必要不可欠ですので、私は「社内インフラの神」になるという第一チェックポイントを掲げて、学習をスタートしました。

 

どう進めたか?

とはいえ現状、AWS関連サービスに関する知識がほぼ無く、マニュアルに従って確認作業をする程度のことしか出来ていない状況です。

まずは書籍やオンラインセミナーによるインプットと、インフラ担当のメンバーから一部作業を引き継ぐというアウトプットに専念しました。

 

最初の課題

そのようにして2ヶ月ほどインフラについて学習を進めていたところ、弊社は東証上場という節目を迎えることになりました。

それに伴い、企業サイトのリニューアルをするという案件が生まれました。

本来であれば経験豊富なシニアエンジニアに任される案件でしたが、20%の学習状況も踏まえて、ほぼ全面的に私が担当することになりました。

 

この機会でやったこと

- Dockerコンテナ作成

- Route53におけるルーティング設定

- Cloudfront CDNの設定

- Codebuild CI/CDの構築

 

2021年12月16日に最初のリリースを行ない、サイトのリニューアルが完了しました。

こちらのサイトが成果物になります。

www.croooober.co.jp

通常の案件ではありますが、20%で得た知見を一つの形として完遂したことが自信に繋がりました。一般的に見ればただの企業Webサイトですが、私にとって1からやりきった初めてのプロジェクトですので、非常に印象に残っています。

 

今後の20%ルールについて

今回行なったことはAWSに特化している上に浅い領域です。クラウドネイティブな時代になっては来ていますが、どのサービスでも通用するであろう、より低いレイヤの知識も身につけていきたい所存です。

また、自分だけの知識とせず、いつか社内で勉強会を開くような形でチームの共有知にしていきたいですね!

 

最後に

今回は私の20%ルールについての紹介でした。

今後、他のメンバーの取り組みもこのブログで発表していきます。

 

ZERO TO ONEでは、システム開発を一緒に楽めるメンバーを大募集しています!

採用情報 | 株式会社クルーバー ZERO TO ONE事業部