転職してakippaに入社しました
前回: クックパッドを退職しnana musicに入社しました - hiragram no blog
こんにちはー。
2024年1月末をもって前職を退職し、2月からakippa株式会社で働いています。3ヶ月の試用期間を経て5月以降も雇用が継続される見込みとなったので、この記事を書いています。
謝辞
前職には2022年2月から2024年1月までのちょうど2年間在籍しました。自分と家族の状況変化と、仕事で負う役割や会社そのものの変化がたまたま近い時期に起こって、自分と家族が健やかに暮らすことを優先するためには環境を変える必要があったという感じです。
在籍中お世話になった同僚の皆様、ありがとうございました。一部の方には、急な告知のあとすぐ居なくなるような形になってしまったことをお詫びします。ご心配をおかけしてすみません。
今はakippaで働いています
一部の方にはまたかよと思われそうですが、現職でも1人目の正社員iOSエンジニアとしてXcodeのアップデートから採用活動までいろいろとやっています。
本社が大阪にあるので、年に何回か行く機会があります(すでに2回行った)。大阪の地理のことはなんにも分からなくて、初めて上京したときのワクワクに似た感覚を覚えます。
転職先を選ぶにあたって、リファラルに頼るかどうかは迷いました(実際にお声をかけてくださる方もいてありがとうございました)が、転職活動をしていた2023年12月当時はフィジカルもメンタルもやられ気味でポキポキしており、 物理的にも所属的にも誰も自分のことを知らない場所へ行きたい気分でした。で実際そういう環境に移って、程よいプレッシャーと、仕事によって信頼をゼロから積んでいく感じに心地よさを感じながら働いています。
いま思うこと
スタートアップの船に乗るには自分はまだ未熟だったと感じます。人や物事を見極める力もなかったし、自分が頑張っていっぱい働けばいい感じの成果が出ると思ってた節があったと思います。そのへんは自分を鍛えてまた何かに挑戦できたらいいなー。
最近久しぶりの方とお会いするシーンが多くて、楽し〜となっています。勉強会などにも出ていきたいと思ってるので見かけたら声かけてやってください。 今後ともよろしくお願いいたします。
クックパッドを退職しnana musicに入社しました
こんにちはー。
2022年1月末をもってクックパッド株式会社を退職し、2月から株式会社nana musicで働いています。 6ヶ月の試用期間を経て8月以降も雇用が継続される見込みとなったので、この記事を書いています(書いてるのは6月末)。
みんな入社していきなり新しい会社を褒めちぎる転職エントリかけるの不思議だなーといつも思っています。
謝辞
クックパッドには2018年8月から2022年1月の3年半在籍しました。感染拡大第6波の最中の退職だったため、多くの方に直接ご挨拶できなかったのが悔やまれます。在籍中お世話になった皆様、ありがとうございました。
横浜移転による片道120分超の通勤時間と週2日のオフィス勤務の組み合わせに適応できなかったのが直接の退職理由です(退職時点での話。今はどうなってるか知らない)。
今はnana社で働いています
冒頭にも書いたとおり、2月から株式会社nana music(以下nana社)で働いています。歌や楽器の演奏を投稿したり他者の投稿に対して重ね録りでコラボレーションしたりできる音楽アプリnanaを開発運営している会社です。
入社当時nana社にはiOSアプリ開発の深い経験があるメンバーがおらず、他プラットフォームのエンジニアがiOSアプリの開発を兼務している状況でした。で、いろいろ課題があっていろいろいい感じにしてくれーという話を頂いて、他にもいろいろあって入社に至りました。
iOSアプリチームのリーダー(他にメンバーはいない)として入りましたが、今はそれに加えてnana開発全体のリーダーの役職も頂いていて、POと開発者の間に入って各プラットフォームのタスク管理をしたり、チーム全体のワークフローを改善したりみたいなこともやっています。
いま思うこと
4年前に転職してクックパッドに入ったときから、クックパッドで最後のレベル上げをして、その次は大きなチャレンジをしようと思ってました。年齢も20代後半の更に後半になったし、結婚もしたし、自分の趣味やこれまでのキャリアと親和性があるようなオファーを頂いたし、じゃあヨイショという感じです。
nanaというプロダクトは2012年から運営されていて1000万を超えるユーザーを抱える一方で、親会社だったDMMから2021年に再び独立したばかりで創業期みたいなワチャワチャ(あえて濁します)が超あるみたいな、プロダクトを応援してくれるユーザーコミュニティと毎日文化祭前夜みたいなヒリヒリ感がどっちもあるみたいな、そんな感じで結構稀有な環境だと思っています。そして強いエンジニアを全方位で欲しています。ネットカルチャー、歌ってみた、VOCALOID、二次創作みたいなキーワードにグッとくる方はぜひ一緒にヒリヒリしましょう。Twitterで僕宛にDMください。会社紹介しろでも社長と面談させろでもなんでも設定します。
コロナ時代に入ってからは開発者コミュニティ活動からちょっと距離を置いていましたが、またワイワイするのが恋しくなってきたのでいろいろ参加していきたいと思います。見かけたら声をかけてやってください。今後とも宜しくお願いいたします。
痔瘻の手術をした
3行
痔瘻とは
"じろう"と読みます。以下のページが大変わかりやすいです。
https://www.iwadare.jp/G-WEB/1-4.html
経過
- 2018年12月に最初の肛門周囲膿瘍ができる。
- しこりの痛み、38度の発熱がある。
- 症状をググりまくったところ肛門周囲膿瘍という病名にたどり着き、近隣の有名っぽい肛門科を受診することにする。
- クリニックを受診、日帰りで切開術を受ける。仙骨麻酔で膿瘍を切開して膿を流す。
- 術前の血液検査で炎症の度合いを示す値が異常に高かったらしい。膿んでるし熱出てるし当然っぽい。
- 切開後は麻酔が抜けるまで3時間ほど病室で安静。傷にはガーゼを当てて自分で運転して帰る。
- 切開によって瘻管が開通し痔瘻が形成される。2泊の入院を伴う根治手術を勧められる。
- 「仕事のスケジュール確認してまた連絡します〜」→チキって逃げる
- 2020年2月末、肛門周囲膿瘍が再発し同クリニックを受診する。
- 「言ったじゃん、根治手術やりなって」「はい…」
- その場ではまた切開を行い、その場で入院と手術の日程を決めて帰宅。
- 2020年3月26日、入院、切開開放術による痔瘻根治手術を受ける。
退院後
痔瘻の根治のために取り除かないといけない瘻管は直腸から肛門付近にかけて存在していて、そのため肛門のすぐとなりに結構な傷を負っている。細菌が滞留しないようにあえて完全に縫合せず傷を開けてあるらしい。2~3時間おきにガーゼ交換をしている。ガーゼが汚れるのはだいたい傷からの浸出液で、たまに思い出したように血が出る。
傷 治りを早める 食べ物 とかで検索したところビタミンCタンパク質鉄亜鉛とかが大事らしいのでとりあえず西友ネットスーパーで豆乳ブロッコリーみかんほうれん草とかを買ってみた。まだ食べていない。
眠ってしまえば途中で起きることは無いが、眠りに落ちるまでと、起きて痛むのがしんどい。
感想など
健康は大事。アルコールの多量摂取や免疫低下などによって便がゆるくなりがちな人は痔瘻を起こしやすいらしい。私は腹下しがちパーソンなのでナルホドという感じ。食生活などをちゃんと見直してみようと思いました。
傷が埋まって完全に支障がなくなるまで1ヶ月から1ヶ月半くらいかかるらしく、これが普通に通勤してる期間だったらマジで生活が破滅していたなと思いました。布団にうつ伏せで仕事ができるし、トイレもオフィスにいるよりずっと近い。いい機会だと思ってちゃんと直します。
2月の日帰り切開処置と3月の入院2泊3日で窓口負担がトータルで11万くらい。生命保険に入院保障と手術保障がついてたので、雑に確認したところ保険金が50万ちょっと入るらしい(本当か?)。iPadかMacBookか迷う。毎回年末調整で保険グエ〜とか言いつつちゃんと払っておいてよかったと思いました。
あと1月に受けた健康診断の結果、肝機能がE(要治療)判定だったので尻なおったら次は肝を直す予定です。対戦よろしくおねがいします。
開発合宿した
仲良しが集まったコミュニティであるところの hiragram.slack.com メンバーで開発合宿をやりました。
メンバーは私, suthio, anoChick, miyachik。場所は湯河原にあるおんやど恵という宿。
出発当日の様子。
本当は踊り子の中でさっそく酒盛りだ〜❗と思ってたけど寝不足で死ぬと思ったのでカルピスにした。
品川から湯河原まで特急踊り子で1時間くらい。
車窓から。
12時くらいに着いたら昼ごはん。
— 社会に爪痕を残すぞ (@hiragram) 2018年10月20日
おいしい pic.twitter.com/JOHaA4AfUX
— あのちっく (@anoChick) 2018年10月20日
— 🍵 (@_miyachik) 2018年10月20日
お店はここです。
https://tabelog.com/kanagawa/A1410/A141002/14029882/
宿。
https://t.co/LGWGpUCapI開発合宿、4人でこのサイズの会議室でのんびりしてる 曲は韻シスト pic.twitter.com/m2FeGZ5kia
— 社会に爪痕を残すぞ (@hiragram) 2018年10月20日
合宿進捗です pic.twitter.com/c9wL1twxu0
— 社会に爪痕を残すぞ (@hiragram) 2018年10月20日
私はFirebase+Vue.js+Stripeでクレカ決済付きのwebページみたいなやつを作ってました。
CSS、全然中央寄せになってくれない
— 社会に爪痕を残すぞ (@hiragram) 2018年10月20日
あとはなんかいい感じのご飯を食べたり、
プロジェクタ借りてマリオパーティしたり、
wifiが届く足湯で作業したりしました。
足湯プログラミングなう
— 社会に爪痕を残すぞ (@hiragram) 2018年10月20日
施設はすごくきれいだし、温泉も夜通し入れるし、wifi足湯があるし、自由に使える会議室めっちゃ広いし、
おんやど恵さいこう〜
— 社会に爪痕を残すぞ (@hiragram) 2018年10月20日
という感じです。
作ったものはもうちょっと整えてそのうち公開します。
いつもhiragramを応援してくださる皆様へ
スタートトゥデイテクノロジーズ(旧VASILY)を退職します。今日から有給消化です。
次は決まっていません。8月からiOSらへんの技術者として雇ってもらえる会社を探しています。
最近業務やら個人やらでやったことは以下の通りです
- RxSwiftの啓蒙
- ビットコインのアービトラージ取引支援アプリみたいなやつ
- OpenAPI(Swagger)ドキュメントからAPI定義/モデル型定義のコードを生成する仕組みの開発
- 任意の状態のスクリーンショットを収集する仕組みの開発
- 消費型課金/サブスクリプション型課金の実装
- CI周りいろいろ
- API/UserDefaults/Keychainなどの抽象層の設計、実装
- try! Swift Tokyo 2018で型安全なWebAPIモデリングについてLT発表
- その他細々した発表
今の所あんまりやりたいと思っていないことは以下の通りです
- 医療
- 金融
新しい環境に強く求めることは以下の通りです
- 有給を使わなくても出社前に猫を病院に連れていけること
出来上がってるチームにあとから入るならめちゃめちゃ凄い人に学ばせてもらえる感じがいいです。スタートアップ博打にも興味はありますという感じです。
もしご興味持っていただける方がおられましたら、お気軽にTwitterまでご連絡ください。
レギュレーションに則ったエントリを出すのが夢だったのですが、求職エントリになりました。
タイトルで煽らない、かしこまった見出しもつけない、ウィッシュリストのせない、東亜飯店張らない、fromとtoを両方書く。職場崩壊を暴露しない。キラキラしない。これが私の求める退職エントリです。
— laiso@7/2〜東京🇯🇵 (@laiso) August 1, 2017
to決まってないなら求職エントリにすればいいですよ https://t.co/vPXDtkmwg0
— laiso@7/2〜東京🇯🇵 (@laiso) July 1, 2018
Vapor + Vapor Cloudでクローラー的バッチ処理
この記事はVASILY Advent Calendar 2017の17日目の記事です。
また、以下の記事の続きです。
アプリにJSONを返す部分を安全にする話を前回したので、今度は取引所のAPIを叩いてデータを取得するバッチを紹介する。
データ集めのバッチもVaporの Command の仕組みで動かしている(ここでは詳細は割愛するが、Vapor Cloudはcronもサポートしている)。
今度はバッチ側が取引所APIのクライアントサイドの位置付けになるので、まずは自作フレームワークAbstractionKit を用いて各取引所APIを抽象化し、RxのObservableを経由してデータを取得するクライアント層を作る。
Coincheckの売買価格を取得するAPIをこう定義する。
struct CoincheckEndpoints { struct CurrentRate: EndpointDefinition { typealias Response = SingleResponse<Element> typealias Environment = CoincheckEnvironment struct Element: Himotoki.Decodable, SingleResponseElement { // 略 } var path: String = "/api/exchange/orders/rate" var method: HTTPMethod = .get var parameters: [String: Any] enum Order { case sell case buy var name: String { switch self { case .buy: return "buy" case .sell: return "sell" } } } init(orderType: Order) { parameters = [ "order_type": orderType.name, "pair": "btc_jpy", "amount": 0.005, ] } } }
EndpointDefinition はAbstractionKitが提供するプロトコルの一つで、各エンドポイントのリクエストパラメータ、レスポンスの型、HTTPメソッド、パスなどを定義する。
次いで、 EndpointDefinition のインスタンスを受けて実際にリクエストを投げ、結果を取得できるObservableを返す request メソッドを作る。
static func request<Endpoint: EndpointDefinition>(_ endpoint: Endpoint) -> Single<Endpoint.Response.Result> { return Single.create(subscribe: { (observer) -> Disposable in DispatchQueue.global(qos: .default).async { do { let response: Response let absoluteURLString = Endpoint.environment.url(forPath: endpoint.path).absoluteString let parameters = endpoint.parameters.mapValues({ (value) -> String in String.init(describing: value) }) switch endpoint.method { case .get: response = try httpClient.get(absoluteURLString, query: parameters) case .post: response = try httpClient.post(absoluteURLString, query: parameters) case .delete: response = try httpClient.delete(absoluteURLString, query: parameters) case .patch: response = try httpClient.patch(absoluteURLString, query: parameters) } if response.status.statusCode >= 300 { // Error observer(.error(NetworkError.uncategorized(message: response.status.reasonPhrase))) return } else if let bytes = response.body.bytes { let data = Data.init(bytes: bytes) guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? Endpoint.Response.JSON else { observer(.error(NetworkError.uncategorized(message: "Failed to cast JSON type"))) return } let result = try Endpoint.Response.init(json: json).result observer(.success(result)) } else { observer(.error(NetworkError.uncategorized(message: "Failed to extract bytes"))) return } } catch let error { observer(.error(error)) } } return Disposables.create { } })
必要になったものから実装しているので、HTTPリクエストの機能をすべて網羅していないが気にしないでほしい。
エンドポイントの定義とリクエストメソッドが揃ったので、以下のように通信を起動する事ができるようになった。
swift let endpoint = CoincheckEndpoints.CurrentRate.init(orderType: .buy) APIClient.request(endpoint).subscribe().disposed(by: bag) ExchangeClient というプロトコルを導入し、取引所APIをジェネリックに扱えるようにする。
protocol ExchangeClient { func currentAskRate() -> Single<Ask> func currentBidRate() -> Single<Bid> }
クロール処理自体はRxSwift+RxBlockingで管理している。結局同期的にやるのだが、Rxに乗せておけば順番を入れ替えたり並列処理に変えたりというのが極めて簡単にできる。楽。
public func run(arguments: [String]) throws { let clients: [ExchangeClient] = [ CoincheckClient.init(), BitflyerClient.init(), ] let askRatesObservable = Observable.combineLatest(clients.map { $0.currentAskRate().asObservable().take(1) }) let bidRatesObservable = Observable.combineLatest(clients.map { $0.currentBidRate().asObservable().take(1) }) let asks = try askRatesObservable.toBlocking().first() // ここで同期的に通信が完了するのを待っている let bids = try bidRatesObservable.toBlocking().first() try asks?.forEach { try $0.makeQuery(driver).save() } try bids?.forEach { try $0.makeQuery(driver).save() } }
Vapor Cloudの一番安いDBインスタンスは、直接SQLクライアントアプリ等からつなぐことが出来ず、phpMyAdminが提供されるだけなのがちょっとつらいが、まあ安いので目をつぶろう、という感じです。
簡単にだが、クローラーバッチの紹介を以上とする。
次は23日に
- SwaggerからSwiftのコードを生成するPython製ジェネレータ
の記事を書くから期待してほしい。
安全サーバーサイドSwift
この記事はVASILY Advent Calendar 2017の9日目の記事です。
目指すのは労働からの引退。
VASILY開発合宿で取り組んだ内容です。
何を作りたいのか
ビットコインの売買価格は取引所によって異なる。そこで、安い取引所で買って、すぐに高い取引所で売ることができれば、ビットコインそのものの価格変動に左右されずに利益を得ることが出来る。これがアービトラージ取引である。
今回は、複数の取引所のAPIを叩いて定期的に売買価格を取得するサーバーサイドアプリケーションと、その情報を表示するiOSアプリケーションを作った。
システム構成
サーバーサイド
- 言語: Swift
- Webフレームワーク: Vapor
- インフラ: Vapor Cloud
- バッチ処理の一部にRxSwift
iOSアプリ
- Swift, RxSwift, APIKit
その他
サーバーサイドのAPIとモデル型はSwaggerのドキュメントで管理し、各エンドポイントに対応するコントローラとモデル定義はSwaggerのYAMLから自動生成する仕組みを作った。具体的にはSwaggerのYAMLドキュメントをパースしてテンプレート通りにコードを生成するジェネレータをPythonで作った。テンプレートはAppleのgybで書く。
「Swaggerドキュメントから自動生成できないAPI/モデル定義はそもそも設計が間違っているのではないか」という仮説が自分の中にあり、それを検証するためのこのプロジェクトとも言える。
このコードジェネレータについてはVASILY Advent Calendar 2017の別枠で改めて記事にするのでぜひ購読しておいてほしい。
現状できているもの
CoincheckとBitflyerの価格を取得して、1BTCを安い方で買って高い方で売ったときの差益を表示している。スクリーンショットは1ヶ月ほど前のものなので、BTCすごいですねという感じ。古いスクリーンショットを出してきたのは、今の価格で表示したら桁が増えた影響でレイアウトがブチ壊れたからである。
アプリ側は特に面白いことはしていないので、この記事ではサーバーサイドアプリケーションについて解説する。
Vaporアプリケーションの開発
Vaporはオフィシャルに提供されたCLIコマンドがあり、Xcodeのプロジェクトファイルの生成やVapor Cloudへのデプロイ、Vapor CloudのDBインスタンスの管理など、開発フローの中で面倒に感じることの多い様々なことをコマンド一発でできるので便利。公式ドキュメントが充実しているので、詳しくは割愛する。
コントローラの自動生成
先述の通り、クライアントアプリに提供する各エンドポイントのコントローラはジェネレータによって自動生成される。
paths: /board/ask: get: tags: - Price summary: Latest fetched price information for each exchanges description: "" operationId: getLatestPriceForEachExchanges parameters: [] responses: "200": description: successful operation content: application/json: schema: type: array items: $ref: "#/components/schemas/Ask"
このようにSwagger側で定義されたコントローラは、以下のように生成される。
// path: /board/ask final class Board_AskController { typealias GetResponse = ArrayResponse<Ask> let drop: Droplet init(drop: Droplet) { self.drop = drop } } extension Droplet { func setupGeneratedRoutes() throws { do { let controller = Board_AskController.init(drop: self) get("/board/ask", handler: controller.get) // (※) } } }
ここで生成されるのは、各エンドポイントごとに1つのコントローラクラスと、Dropletと呼ばれるVaporアプリケーションのコアにルーティングを登録するコードである。賢明なSwiftエンジニアの読者は気づいたかもしれないが、このままでは(※)の行でコンパイルエラーになる。ルーターに対して /board/ask
へのリクエストを Board_AskController
の get
というメソッドに流すように登録しているが、生成されたコードにはそのようなメソッドは無い。
これがこの仕組みの最も気に入っているところで、Swagger上で定義されたモデル型のレスポンスを返すメソッドをデベロッパーが適切に実装しないとサーバーサイドアプリケーションのコンパイルが通らないのである。すなわち、Swaggerで定義されたエンドポイントを適切に実装していないと、デプロイはおろかコンパイルすら通らないので、あとになって実装漏れが発覚するとか、Swaggerと違う型のオブジェクトが返されてアプリ側が困ることは無い。
コンパイルを通すために、Ask
型のオブジェクトを配列で返す get
メソッドを実装する。
extension Board_AskController: GetRequestHandler { func get(request: Request) throws -> ArrayResponse<Ask> { let exchanges = try Exchange.makeQuery().all() let prices = try exchanges.map { try Ask.makeQuery().filter("exchange_id", $0.id).all().last } .flatMap { $0 } return ArrayResponse.init(element: prices) } }
これで、「DBにある各取引所の最新の買値データを配列にして返す」というエンドポイントが実装された。Ask
の配列以外を返すとコンパイルは通らないので、間違った型のデータを返してしまう心配も無くなった。
ここまでまとめ
- Swagger駆動開発、今のところ良いです
- サーバーもクライアントもSwaggerを神ドキュメントとして扱う風習ができれば、業務に投入したい仕組みである。
この先
VASILYのアドベントカレンダーを3枠もらっているので、あと2枠で
の話を書く。お楽しみに。