2023.02 日々の学び
Command Query Separation
メソッドを2種類に分割するデザインパターン
- Query:状態変化を伴わない. 冪等
- Command:状態変化する. modifierやmutatorと呼称すべき
Command Query Responsibility Segregation
モデルを分離するデザインパターン
- Queryモデル:only read from database
- Commandモデル:update database
参考文献
Dartの非同期処理
- Dartはシングルスレッド
- Queueは2種類
- Event Queue
- Microtask Queue
- Microtaskが全て処理された後、Eventが処理される
参考文献
コンウェイの法則
システム設計は組織のコミュニケーション構造と類似する
逆コンウェイの法則
望ましいシステム設計になるように、組織の構造を設計する
参考文献
Json Web Token
What
HTTPヘッダーやクエリパラメータなどスペースに制約がある環境で使うことを前提に、JSON形式のデータをURLセーフでコンパクトな形式にしたもの
- URLセーフする
- JSONデータをBASE64URLエンコードする
- コンパクトにする
- よく使われるデータ項目の名称を省略形にすることでJSONのキー名を短くする。またこれによりJSONデータをコンパクトにする
Why
HTTPヘッダーやクエリパラメータにJSONデータをうまく載せられるようにするため
Json Web Signature
What
Why
JWTの改竄に気づけるようにするため(防ぐわけではない)
データの盗聴・改竄は可能である点に注意
改竄に気付けるので、リソースサーバーから認可サーバーへの問い合わせが不要になる
資料
ArchiveをRSSでslack通知したい
資料
Github ActionsをLocalで実行
brew install act
act --job generate --container-architecture linux/amd64
M1 Macのエミュレータだと動かないDockerコンテナがあるぽい
Github ActionsがLocalで落ちてたのは多分これ
git diff
git管理されているファイルかつaddされてない変更
git diff --name-only HEAD
addした変更
git diff --cached --name-only HEAD
commitした変更
git diff --cached --name-only HEAD^ HEAD
資料
Flutter WidgetのLifecycle
StatefulWidget
資料
Refresh Token
Access Tokenの有効期限が短いため、頻繁に期限切れとなってしまい、アクセストークンの取得フローも頻繁に実行する必要があります。そこで、Refresh Tokenを利用して煩わしい部分を回避するという理由のために存在します。
資料
Platform依存なパッケージを使ったFlutterのUnit Test
「ストレージに書き込む」処理がUnit Test内にあると
PlatformException(channel-error, Unable to establish connection on channel., null, null)
channelにつながらないよってエラーになる。
マルチプラットフォームなpluginの仕組み
Flutter pluginのテスト
- Flutter pluginは2つのパートを持つ
-
- APIとなるdartコード
-
- プラットフォーム依存(kotlin, swift)なコード
-
2番目のプラットフォーム依存コードはflutterビルドで行われるため、unit testやwidget testでは利用できない.
これを避けるための方法は4つある
- プラグインをWrapした独自クラスをモックする
- プラグインのAPIをモックする
- プラグインのPlatform Interfaceをモックする
- Platform Channelをモックする
- デメリット
- プラグインの内部実装をモックすることになるので、プラグインのアップデートの影響を受けやすい
- Platformごとにモックする必要があり、テストを実行する環境に依存する
- Platform Channelは型が辞書の場合があり、正しくモックするのがめんどくさい
- デメリット
path_providerのunit test
資料
- https://github.com/flutter/flutter/issues/96460
- https://github.com/flutter/flutter/issues/95970
- https://docs.flutter.dev/development/packages-and-plugins/plugins-in-tests
- https://docs.flutter.dev/development/platform-integration/platform-channels
- https://docs.flutter.dev/release/breaking-changes/mock-platform-channels
- https://docs.flutter.dev/cookbook/persistence/reading-writing-files#testing
- https://pub.dev/packages/path_provider#testing
- https://github.com/flutter/plugins/blob/main/packages/path_provider/path_provider/test/path_provider_test.dart
なぜスタートアップ初期のソフトウェア設計は壊れがちなのか
資料
Man In The Middleプロキシ
Why
デバッグ用に通信の監視を行うため
What
特定のサーバーへのリクエストが必ず通過するようにしたプロキシ
資料
Four Keys
- 速度
- デプロイの頻度 - 組織による正常な本番環境へのリリースの頻度
- 変更のリードタイム - commit から本番環境稼働までの所要時間
- 安定性
- 変更障害率 - デプロイが原因で本番環境で障害が発生する割合(%)
- サービス復元時間 - 組織が本番環境での障害から回復するのにかかる時間
資料
Github Actionsのトリガー push or pull_request
- pushイベントではpathsが利用できない
- pushがトリガの場合、常にHEADとそのひとつ前の差分を見る
- pull_requestはマージ元との差分を見る
on:
pull_request:
types:
- opened
- reopened
- synchronize
paths:
- frontend/lib/**
- frontend/test/**
- frontend/pubspec.yaml
- frontend/api/client/**
branches-ignore:
- renovate/**
workflow_dispatch:
資料
Flutter CI(キャッシュなし)
name: Frontend Flutter CI
defaults:
run:
working-directory: frontend
on:
pull_request:
types:
- opened
- reopened
- synchronize
paths:
- frontend/lib/**
- frontend/test/**
- frontend/pubspec.yaml
- frontend/api/client/**
branches-ignore:
- renovate/**
workflow_dispatch:
jobs:
flutter-ci:
runs-on: ubuntu-latest
# runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
# - name: Set Flutter Version to Environment Variable
# run: echo "flutter_version=$(cat .fvm/fvm_config.json | jq -r .flutterSdkVersion)" >> $GITHUB_ENV
# - name: Search Flutter Cache
# uses: actions/cache@v2
# with:
# path: opt/hostedtoolcache/flutter
# key: ${{ runner.os }}-flutter-install-cache-${{ env.flutter_version }}
- name: Install dart
uses: dart-lang/setup-dart@v1.4
- name: Install fvm
run: dart --version
- name: Check Dart Version
run: dart pub global activate fvm
- name: Setup flutter
run: fvm install
- name: Check Flutter version
run: fvm flutter --version
- name: Install dependencies
run: fvm flutter pub get
- name: Add dotenv
run: |
touch .env
touch .env.dev
# - name: Analyze
# run: |
# fvm flutter analyze --no-fatal-infos
# fvm flutter pub run dart_code_metrics:metrics analyze lib --no-fatal-warnings
- name: Test
run: fvm flutter test --coverage --coverage-path=coverage/lcov.info
- name: Show Coverage Report
uses: zgosalvez/github-actions-report-lcov@v1
with:
coverage-files: coverage/lcov.info
minimum-coverage: 0
artifact-name: code-coverage-report
github-token: ${{ secrets.GITHUB_TOKEN }}
Github Actionsのキャッシュ
資料
- https://blog.dalt.me/3249
- https://docs.github.com/ja/actions/using-workflows/caching-dependencies-to-speed-up-workflows#comparing-artifacts-and-dependency-caching
- https://zenn.dev/fastriver/articles/05f1780c7ba28a
- https://github.com/actions/upload-artifact
- https://github.com/actions/download-artifact
- https://github.com/marketplace/actions/html-preview
- https://github.com/zgosalvez/github-actions-report-lcov
ErrorとException
- Error
- 回復不可能. プログラムを終了させる.プログラムそのものに問題がある. プログラムエラー.
- Exception
- 回復可能. プログラムに何とか処理してほしい. 実行時エラー.
資料
mockito / NamedArgumentを固定してないとNo stub was foundになる
問題
whenでstubしてるにも関わらず
FakeUsedError: 'myFunction'
No stub was found which matches the argument of this method call:
final mockError = Exception();
when(
mockApiClient.myFunction(),
).thenThrow(mockError);
引数も特にいらないはずと思い込んでいた。
Future<ClientResponse> myFunction({int? paramA=1}) async {...}
解決策
final mockError = Exception();
when(
mockApiClient.myFunction(
paramA: anyNamed('paramA'),
),
).thenThrow(mockError);
資料
モノレポ構成の時に、特定のworkflowでBranch Protectする
CIの成否でブランチプロテクトしたいが、モノレポ構成なのでフロントエンドのCIによるプロテクトがバックエンドにも影響する
Branch Protection Rules を通すには、成功したように見せかける必要があります。そのため、job 単位では実行する必要があり、step 単位ではスキップしていくという流れになります。
なるほど。。。
資料
Flutter / ログアウト・アカウント削除をした時はpushNamedAndRemoveUntilで履歴を消す
RoundButton(
onPressed: () {
Navigator.of(context).pushNamedAndRemoveUntil(
Routers.startPage,
ModalRoute.withName(Routers.root),
);
},
ページが消えないとstateが初期化されない。
なので1回目のアカウント削除は成功するが、2回目は失敗することがある。
資料
DartのLinter
- Why
- エラーを避けるプラクティスパターンのコードを担保する
- What
- コードに問題点がないかを確認する静的解析ツール
fvm flutter analyze --no-fatal-infos
fvm flutter pub run dart_code_metrics:metrics analyze lib --no-fatal-warnings
DartのFormatter
- Why
- 統一された書き方の読みやすいコードを担保する
- What
- コードのスタイルをチェックするツール
fvm flutter pub run import_sorter:main {staged_files}
fvm flutter format --fix {staged_files}
資料
Lefthookで、HEADとremoteとの差分があればトリガーしたい
git diffで頑張る方法 → 失敗
set upstreamしてない1回目のプッシュでは失敗する
git diff --name-only HEAD @{push}
fatal: ambiguous argument '@{push}': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
post-checkoutした時にリモートブランチを強制的に作る → 微妙
post-checkout:
commands:
set-upstream-branch:
run: git push
pre-pushトリガーは、変更ファイルがないので起動しない。
副作用がありそうで怖い方法だな・・・
defaultブランチとの差分でトリガーする
余分な実行はありそうだけど仕方ない
git diff --name-only HEAD develop
資料
Lefthook Config
Git Hook
filesは2ヶ所にあるので注意
runで使える変数
- files
- 任意のコマンド結果を保持する
- push_files
- all_files
- staged_files
const (
cmdRootPath = "git rev-parse --show-toplevel"
cmdHooksPath = "git rev-parse --git-path hooks"
cmdInfoPath = "git rev-parse --git-path info"
cmdGitPath = "git rev-parse --git-dir"
cmdStagedFiles = "git diff --name-only --cached"
cmdAllFiles = "git ls-files --cached"
cmdPushFiles = "git diff --name-only HEAD @{push} || git diff --name-only HEAD master"
infoDirMode = 0o775
)
Lefthookのexclude
正規表現でファイルやディレクトリを指定すると、{files}から対象が削除される
対象から消えているかどうかは lefthook run -v
で確認できる
lefthook -v run pre-commit
Lefthook v1.2.1
RUNNING HOOK: pre-commit
Files before filters:
[frontend/api/client/sample.dart frontend/lib/sample.dart frontend/lib/sample.freezed.dart frontend/lib/sample.mocks.dart]
Files after filters:
[./lib/sample.dart]
Files after escaping:
[./lib/sample.dart]
Executing command is: fvm flutter pub get && fvm flutter pub run import_sorter:main ./lib/sample.dart && fvm flutter format --fix ./lib/sample.dart && git add ./lib/sample.dart
pre-commit:
parallel: true
commands:
flutter-format:
root: "frontend/"
files: git diff --cached --name-only HEAD -- '*.dart'
glob: "*.dart"
exclude: '(?:\.(?:freezed|g|mocks|gen)\.dart|frontend\/api)'
run: >
fvm flutter pub get &&
fvm flutter pub run import_sorter:main {files} &&
fvm flutter format --fix {files} &&
git add {files}
正規表現
資料
- exclude
- filterExclude
- prepareFiles
dioをmockして自動生成したOAS Clientからのmockとする
OAS Clientそのものをmockすると、mock OAS Clientのrequestとresponseを作る必要がある
自動生成されたクラスを組み合わせてrequestとresponseを作るのは面倒なので、APIからのjsonレスポンスを作る方が楽かと思い実験。
// Package imports:
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:openapi/openapi.dart';
final dioProvider = Provider<Dio>((ref) {
final dio = Dio(
BaseOptions(
baseUrl: '/api/v1',
headers: <String, dynamic>{'Accept': 'application/json'},
),
);
return dio;
});
final openapiClientProvider = Provider((ref) {
final dio = ref.read(dioProvider);
return Openapi(dio: dio);
});
group(
'Repository Test',
() {
late ProviderContainer container;
late DioAdapter dioAdapter;
final dummyEndpoint = '/books';
setUp(
() async {
final dio = Dio(
BaseOptions(
validateStatus: (status) => true,
),
);
dioAdapter = DioAdapter(dio: dio);
container = ProviderContainer(
overrides: [
dioProvider.overrideWithValue(dio),
openapiClientProvider,
apiProvider,
repositoryProvider,
],
);
},
);
group(
'fetchBooks',
() {
test(
'[正常系]',
() async {
// Dio Client Mock
dioAdapter.onGet(
dummyEndpoint,
(server) {
return server.reply(
200,
<String, dynamic>{
'list': [
{
'id': 123,
'title': 'ティアムーン帝国物語',
},
]
},
);
},
);
final repository = container.read(repositoryProvider);
final response = await repository.fetchBooks();
response.when(
success: (value) => expect(value.length, 1),
failure: (error) =>
fail('Processing is not expected to be failure'),
);
},
);
},
);
},
);
資料
Flutter Integration Test
Finder
Widgetを探す方法
WidgetsFlutterBinding / IntegrationTestWidgetsFlutterBinding
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
WidgetsBindingとは
The glue between the widgets layer and the Flutter engine.
runApp()を呼び出す前にFlutter Engineの機能を利用したい場合にコールするらしい
資料
yaml
アンカーとエイリアス初耳
common: &common
aaa: 1111
bbb: 2222
dev:
build: *common
environment: aaa
stg:
build: *common
environment: bbb
prod:
build: *common
environment: ccc
資料
Flutter / 画像の読み込み
CircleAvatar(
backgroundImage: NetworkImage(avatarUrl),
radius: radius,
);
CachedNetworkImageはもう古いらしい
Image.network(
avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container();
},
);
FlutterのImage.networkはキャッシュされているのか
ImageProvider
NetworkImage
Image.network
Flutter / Integration TestするときFirebaseをどう初期化する?
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
initializeしているはずなのだけど・・
[core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp()
こうかも
void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: dev_firebase.DefaultFirebaseOptions.currentPlatform,
);
資料
Flutter / キーボード表示でpixel overflow
Scaffold(
resizeToAvoidBottomInset: false,
資料
integration_testのサンプルコード
WidgetTester
tester.pumpWidget(const MyWidget());
tester.pump();
tester.pumpAndSettle();
NeRF
Representing Scenes as Neural Radiance Fields for View Synthesis
モデル
Radiance Fields = NeuralNet(空間の座標, 視線の角度)
輝度(色と密度) = NeuralNet(空間の座標, 視線の角度)
学習
これはカメラ
カメラからある物体を見たときに、光の通る道の「輝度」をモデルが出力する
道の上の輝度を積分すると、カメラから見た画像の1ピクセルの色と一致する
なので、カメラ方向から輝度を積分して得た画像と、実際の画像との差分が小さくなるように学習する
NeRF研究の分類
資料
One-shot Implicit Animatable Avatars with Model-based Priors
1枚の写真から自由に動かせるアバターを作るらしい
資料
DNS
ドメイン名とIPアドレスとの対応づけを行う仕組み
TLD: Top Level Domain
example.co.jp
の jp
FQDN: Fully Qualified Domain Name
www.amazon.com
のようにホスト名 www
+ ドメイン名 amazon.com
のこと
DNSサーバーの種類: キャッシュDNSサーバー + 権威DNSサーバー
- キャッシュDNSサーバー
- クライアントからのリクエストを受け付ける
- 権威DNSサーバー
ICANN
DNSルートサーバーは世界で13ヶ所
*.root-servers.net
というホスト名を持つ
*にはaからmまで入る
日本のDNSルートサーバーは m.root-servers.net
dig
Domain Infomation Groper = dig
8.8.8.8
で動作しているDNSサーバーに google.com
の名前解決をリクエストする
dig google.com @8.8.8.8
8.8.8.8
はGoogleが公開しているDNSキャッシュサーバー
逆引き:IPアドレスからドメインを検索する
dig -x 8.8.8.8
trace
dig @8.8.8.8 +trace stackoverflow.com
資料
gTLD:Generic Top Level Domain
国や地域によらないトップレベルドメイン(TLD)
com, info, net, org, biz, name, pro
glue records
権威DNSサーバーがcom
を管理していて、一部のドメイン google.com
を別の権威DNSサーバーに委任している時
「委任先のネームサーバーに聞いてくれ」とクライアントに返答するときに付加する情報のこと。
これがないと、クライアントがたらい回しになる。
委任
ASN: Autonomous System Number
AS番号
ネットワークの識別番号のようなもの?
イメージは郵便番号らしい
経路制御方針を共有したネットワークの集合体
IPアドレス管理
Anycast
リクエストの負荷分散をする仕組み
JM Project (Linuxの日本語マニュアル)
BitriseでiosアプリをApp Storeにデプロイ
Bitriseを使わずに手動でアップロードする方法
Flutterアプリの難読化
flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory>
Bitriseにアップロードできるgeneral filesは5件がマックスで拡張できない
zipfileで圧縮してワークフロー内で解凍する回避方法がある
secretファイルをgithub管理する
目的
- secretファイルがなくて動かない
- secretファイルを持ってる人からDMで送ってもらう
- そもそもsecretファイルが必要なことに気づかない
といった面倒を回避したい。
一方でsecretファイルが流出することのないようにしたい
方法
-
git-secrets
https://git-secret.io/ -
GnuPG
https://gnupg.org/
を使う
手順
brew install gnupg
gpg --gen-key
brew install git-secret
Finderでドットファイル表示
defaults write com.apple.finder AppleShowAllFiles TRUE
killall Finder