初めてのTech DayでConcourse CIに入門した
コイニーのバックエンドチームでTech Dayを初開催しました! Tech Dayとは、「日々のプロジェクトにリソースを取られて、技術獲得やチャレンジができず知的好奇心を満たせていない」と感じたリーダーが(勝手に)企画してくれたとてもありがたい日です。2週ごとに1日くらいのペースでやっていくつもりです。 題材は、自チームや自身のメリットに繋がることであれば何でもよいです。(他チームの為に便利ツールを作成するとか運用が発生するようなものはやらない)
私は題材に「Concourse CI入門」を選びました。チュートリアルを試したレベルですが、やったことを書きます。 この題材にした理由は2つあります。
- 数年前から気になっていて、当時の同僚(とても尊敬している人)がかなり推していたから
- 会社でJenkinsを使っているけど、時代的にどうだろうと思ったから。
Concourse CI入門
チュートリアル( Introduction - Concourse Tutorial by Stark & Wayne )をやりました。
Concourseをローカルにデプロイ
- Docker Composeのインストール
- 任意のディレクトリで次のコマンドを実行。Councourseがデプロイされます。
$ wget https://raw.githubusercontent.com/starkandwayne/concourse-tutorial/master/docker-compose.yml $ docker-compose up -d
fly CLIのセットアップ
flyコマンドについては、こちら concourse-ci.org
fly CLIをダウンロード
http://127.0.0.1:8080/にアクセスして、OSのロゴをクリックするとfly CLIのバイナリが落ちてきます。(今回はMacで話を進めます)
ダウンロードしたら次のコマンドを実行します。
$ sudo mv ~/Downloads/fly /usr/local/bin $ sudo chmod 0755 /usr/local/bin/fly
ターゲットの指定
fly CLIは、declaring absolutely everything you do to get absolutely the same result every timeの精神に基づいており、flyコマンドを実行する度にターゲットAPIを指定する必要があります。
ターゲットエイリアスを作成
// tutorialという名前でエイリアスを作成 $ fly --target tutorial login --concourse-url http://127.0.0.1:8080 logging in to team 'main' navigate to the following URL in your browser: http://127.0.0.1:8080/sky/login?redirect_uri=http://127.0.0.1:54711/auth/callback or enter token manually:
メッセージにしたがってhttp://127.0.0.1:8080/sky/login?redirect_uri=http://127.0.0.1:54711/auth/callbackにアクセスすると、ログイン画面が表示されます。
docker-compose.ymlのservices.concourse.environment.CONCOURSE_ADD_LOCAL_USER
に設定されている。username:admin、password:adminでログインできます。
コマンドでもログインできます。
$ fly --target tutorial login --concourse-url http://127.0.0.1:8080 -u admin -p admin logging in to team 'main' target saved
ターゲットとして指定されているConcourseと同じバージョンのflyコマンドにアップグレード
$ fly --target tutorial sync version 4.2.1 already matches; skipping
ターゲットの確認
$ cat ~/.flyrc targets: tutorial: api: http://127.0.0.1:8080 team: main token: type: Bearer value: eyJhbGciOiJSUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJjc3JmIjoiNTEzZGIyZTBiMGVkYTEzZGU5MDk5Mzk0YjJkMWNiMTViZDQ2ZTJjZTgzZjRhOTFjYjRmMmQzZjY4ZmFmOWE5YyIsImVtYWlsIjoiYWRtaW4iLCJleHAiOjE1NDcyNzcyMzUsImlzX2FkbWluIjp0cnVlLCJuYW1lIjoiIiwic3ViIjoiQ2dWaFpHMXBiaElGYkc5allXdyIsInRlYW1zIjpbIm1haW4iXSwidXNlcl9pZCI6ImFkbWluIiwidXNlcl9uYW1lIjoiYWRtaW4ifQ.n-3zISYO7poQamnjJ-yYP-ChX3RHROTWkwng3GnHm3d8fefxoO8p-Bxod2we-KQWEfE8w27JuyNejOI53dSK7rORi1bu8xUE7WbLkhZTA1TlvTzPj8sEXT-KJ_PfCBgyhs_vL91LertLkXVA0Orre6aRGb-kHd-tbdqF1OaMFGYKZbJ5oZr0hMtvunQrpm3oIbeHEX0Edvwi2tYDem4ccwqrhoxSEYVouXuBthdq59LeBw5INsM92UIBPQNkZqkSZyQCJqaSzr3xeGDiDxWl5uxIfeAFHfOUSuqefTE1bhWS3dolRDWr5SDWi_oIuW1fCMmsY8bWMrWGnCatI4tXuQ
エイリアスの作成をしたので、flyコマンド実行時に fly --target tutorial
とうつことでこのConcourse APIをターゲットにできます。
Taskを実行する
task_hello_world.ymlの実行
任意のディレクトリで次のコマンドを実行し、concourse-tutolialをクローンしてtask_hello_world.ymlを実行します。これは、echo hello world
と出力するだけの簡単タスクです。
$ git clone https://github.com/starkandwayne/concourse-tutorial $ cd concourse-tutorial/tutorials/basic/task-hello-world $ fly -t tutorial execute -c task_hello_world.yml
タスクが完了すると、以下のように出力されます。echo hello world
が呼ばれていることがわかります。
executing build 1 at http://127.0.0.1:8080/builds/1 initializing running echo hello world hello world succeeded
http://127.0.0.1:8080/builds/1にアクセスするとWebUIで実行結果を確認できます。
task_ubuntu_uname.ymlの実行
$ fly -t tutorial execute -c task_ubuntu_uname.yml executing build 4 at http://127.0.0.1:8080/builds/4 initializing running uname -a Linux 303a03ea-bb52-406c-6da3-93235f258a59 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux succeeded
これも同様に、http://127.0.0.1:8080/builds/4にアクセスするとWebUIで実行結果を確認できます。
今回はとりあえずここまでです。
SpringBoot1系から2系に移行しててハマった点
Spring Boot2系に移行してハマった点について書きます。ほぼ備忘録です。ついでなので2.0.x -> 2.1.xで変更された点もまとめます。
変更点見つけたら随時書いていこうと思います。
移行前のSpring Bootのバージョンは1.5.10です。2系のバージョンは、2.1.1(2018/12/14時点の最新)です。
Web
ErrorAttributes#getErrorAttributes()の引数変わった
Gradle
実行可能jarの作り方
1
bootRepackageタスクが呼ばれたあとにjarタスクが呼ばれて実行可能jarが作られるようです。
bootRepackage {
executable = true
}
2
bootJarタスクで実行可能jarを作ります。jarタスクは無効になっているようです。
https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/gradle-plugin/reference/html/#packaging-executable-and-normal
bootJar { launchScript() }
actuator
エンドポイントプレフィックス
1
/health
2
/actuatorがつくようになりました。
https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/html/production-ready-endpoints.html
/actuator/health
logging
logback-spring.xmlのProfile指定方法
1 ~ 2.0.x
「,」区切りでした。
<springProfile name="dev, staging"> <!-- configuration to be enabled when the "dev" or "staging" profiles are active --> </springProfile>
2.1.x
<springProfile name="dev | staging"> <!-- configuration to be enabled when the "dev" or "staging" profiles are active --> </springProfile>
コイニーとFCPと -副業はじめました-
コイニー(本業)で開発をしながら、FASHION CHARITY PROJECT(FCP)というサービスの開発を副業で始めました。
FCPとは
FCPとは、「ファッションアイテムの寄付とお買い物でチャリティ活動できる」サービスです。
コイニーに入る前に勤めていたwajaという会社が運営しているサービスです。
詳しくは、ココをみてください。
バックエンドは1人で1ヶ月半でフルリプレースを行いました。これは、エンジニア生活で一番の大仕事でした。
技術的な変化としては次の通りです。
(旧)
- Spring Framework 4.1.x
- Spring JDBC
- JSP
(新)
- Spring Boot 2.0.x
- Spring Data JPA
- Spring Security
- Thymeleaf
ただリプレースするだけでなくて、リファクタリングをやったり、パフォーマンス改善も行いました。
なぜ副業を始めようと思ったのか
副業を始めた理由として、もちろん「お金が欲しい!」という気持ちはあります。が、1番の理由は、「転職をしないため」です。
自分は、1つの環境だけに長くいられない性格をしているようです。これまで転職を何度かしてきました。自分でいうのもアレなんですけど、その期間でちゃんと貢献はしてきたつもりです。いろんな会社を見てきた経験はできました。しかし、それとは引き換えに長い時間いることでしか味わえない本当に辛い時期や信頼関係というものを経験したことがありません。
これについては、そろそろ経験しておかないとまずいなとは思っています。
コイニーの所属するheyグループは、まだまだ発展途中ですが本当にいい会社だと思います。
けっこうなペースで人が増えていて、これから組織的な強化が行われていくのだと思います。
それに伴って懸念していることがあります。いい環境というのは心地よくて、長くいたい(出たくない)と思うようになり、外を知るということを自然としなくなるのではないだろうかと思います。そうなるのが非常に怖くて、会社とは違うどこかに所属しておきたいと感じていました。そうすることで転職への熱はだいぶ抑えらます。
なぜFCPを選んだのか
3つ理由があります。
スーパーできるディレクターともう1度仕事をしたかったから
この人と入社時から絡むことはあったけど、仕事で絡んだのはやめる3ヶ月前からです。仕事の熱とか視点とか知識はかなり持っていて、純粋に「この人すごいな」ってなりました。仕事だけでなく、その他の引き出しも多いし何者なのだろうかと思ってました。
ある程度コード書けるので、1人で回せなくはないかなと思います。しかし、リプレースしたばっかで数字もよくて、「今がチャンスだからディレクター業に専念したいから可能なら開発はやって欲しい」というのは退職してからも話は持ちかけられてました。
本業の試用期間も終わって少し落ち着いたころに直で、CEOにFCPの開発やらせてよって連絡しました。思ったよりすんなりOKでました。
そんな感じで始まって、また一緒に仕事ができるのでとてもワクワクしています。
自分が作ったサービスは愛着がわいていたから
だいたいの人はそうなんじゃないかな?って思うんですけど、DB設計からリリースまでほぼ1人でやりましたのでとても愛着が湧いてるサービスです。この経験ってなかなか積めないし、社会に貢献できるサービスなので、これからも成長させていきたいと考えてます。
小さいサービスだしいろいろと試せて、会社では学べないことを学べそうだから
会員数はけっこういますけど、サービスの規模は小さめなので技術的にも施策的にもいろいろと試せるのではないかと考えています。
上記のディレクターとほぼ2人で進めて行くので、ビジネスのこともかなり理解しないといけないです。降りてきた仕様をそのまま実装するのはよくないですし。そしてスピード感も必要。ビジネスの話はあまり得意でないので、そこについても学んでいけるのはメリットが大きいです。
ここで学んだことを本業で活かせるタイミングはあるはず。
最後に
開業freeeを使えばすぐに開業届作れます。税務署での手続きも5分で終わります。
スピーカーとして参加してきたSpring Fest 2018
10/31に開催されたSpring Fest 2018で「Amazon Cognito使って認証したい?それならSpring Security使いましょう!」というタイトルで話してきました。
資料は、これです。
なぜ話すことになったのか
Spring Festを運営しているJSUGから「何か技術的な話ある?」と連絡が来たからです。声がかかったのは純粋に嬉しかったし、人前話す機会はなかなかないので迷うことなくYESと答えました。(人前で話すのかなり苦手ですけど)
〜当日まで
私は日常的に人前で話してないし、ネタになるような勉強はそこまでしていないので1ヶ月半前くらいから準備をはじめました。
サンプル作って、リファレンス読んでソースコード読んでをひたすら行ってネタ集めをしました。ネタ集めを進めていくと、わかっていたこと、わかっていなかったこと、勘違いして認識していたこと、全く知らなかったことがすごくはっきりわかります。
認可周りのは知識ゼロでした。登壇の機会がなかったらたぶん勉強しなおさない部分なのでよかったです。AccessDecisionVoterを使って認可処理を行っている何て想像もしてませんでした。
メソッドセキュリティは好きですが、@PreAuthorize以外がほぼ使っていなくて改めて他のアノテーションについても学べました。
ある程度調査が済んでからは、Keynoteと戯れる毎日。見た目、フォント、図すごく迷いました。(スライドの色は2日前まで全く違う色でした)
社内リハもしました。人に見てもらうと全然目をつけていない部分にもちゃんとアドバイスが来るのでほんとによい。そんなこんなで前日。3回くらい個人リハやりました。
資料作りながら感じたことです。
資料作ったり練習したりして45分セッションやる人すごいなって感じた。
— Ryosuke Uchitate (@b1a9idps) 2018年10月22日
20分だと何か1つでも持ち帰ってくれーくらいでやってたけど、45分となると聴く人が飽きないようにとか、何を聞きたいかとかより考えないといけない。
当日
スピーカー控え室に行きました。スピーカーの方は、みんなギリギリまで資料を見直したり、個人リハをしたりしています。登壇慣れしてる方でも。
自分のセッション
14:15、セッションの時間になりました。立ち見が出るレベルに満席でした。足を運んでくださって本当にありがとうございました。
セッションの頭でコイニー知ってるかのアンケートとってみましたけど、10/90と想像以上に少なくて凹みました...
満席だし、Springの講師やってる多田さんの前だし緊張MAXでした。本当は話すつもりなかったメソッドセキュリティの話もするくらいに時間早く終わってしまった...結果1分しか余らなかったのでよしとしました。
次の多田さんのセッションにパス出せたのかもしれない
僕のセッションではSpring Securityの基本は説明しないので、内立さんのセッションで説明されてて助かった #jsug #sf_23
— Masatoshi Tada (@suke_masa) 2018年10月31日
懇親会
美味しいご飯ありがとうございました。なぜか司会をやりましたw
懇親会でお話した2人が自分のセッション聞いてくださっていました。
1人は、Spring Security特に使ってないけど今後使うことになりそうだからという理由でした。Springのソースコードの解説はよかったとのことでした。
もう1人は、普段からSpring Security使ってるけど自分の理解に自身がなかったし、認証認可周りで知らないことあるからという理由でした。
「自分の理解はあっていたようだ、知らないとこについても知れた。フレームワークのソースコードについても話してくれてありがたかった。メソッドセキュリティは全く知らなかったけど便利そうだから使いたい。」という嬉しい言葉をたくさんいただきました。
こんな風にいろんな人話す機会がある懇親会っていい時間だなと強く感じました。
反省等
アンケートの結果には悪かったという評価もありました。とてもありがたいですが、欲を言えばどこが悪かったかまで書いてもらえるとありがたいです。
自分なりに考えた悪かったところ
- タイトルにCognitoと入れてたけど実際に話したのは、概要くらいで実装は特に紹介しなかった。Spring Security
の話が割合として多すぎた。
- なぜとか考察が少なかった。淡々と解説をしている感じになっていた気がする。今後にどう活かせばいいのかというイメージを描きにくかったと思います。
- 話の流れがうまく繋がっていなかった部分があった。
よかったこともいくつかありますが、恥ずかしいので省略します。
自分で満足するセッションができなかったのは悔しかったので、社で開催する勉強会で反省点を生かしつつ、場数踏んでリベンジします。
その他
アンケートを書くまでがカンファレンスです。フィードバックをもらうことで次の機会に生かすことができます。スピーカーの皆さん単に話したいことを話しているわけではないと思います。何かしら持って帰って仕事に活かしてほしいと思いながら準備・セッションをしています。聴く人が求めていることはなんだろうといったことも考えていると思います。そういうわけで聴いてる人の声がすごく役立つのです。
会全体のアンケートも同様です。3分もあれば終わるので、お礼の意味を込めてアンケートに回答するくらいのことはやってもいいのではないのかな?と思います。
Hibernate Enversで履歴管理
エンティティが登録、更新、削除された履歴を管理します。
Hibernate Enversは、履歴管理テーブル全体を管理するエンティティ(デフォルトでRevinfo)をいて、自動採番されるid(プロパティ名はrev)と履歴を永続化した日時(プロパティ名はrevtstmp)の2プロパティを持っています。
エンティティ(後述のClient)の登録、更新、削除された履歴を管理するエンティティ(後述のClientHistory。デフォルトのクラス名はClientAud)が履歴管理するプロパティに加えて、Revinfoのid(デフォルトのプロパティ名はrev)とどのようなイベントが行われたかのフィールド(デフォルトのプロパティ名はrevtype)を持っています。ちなみにrevは、Revisionの略です。
やること
hibernate-enverを依存関係に追加する
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> </dependency>
@Auditedをエンティティクラス(またはエンティティの特定のフィールド)に付与する
今回は、Clientエンティティを使って説明します。
Client.java
@Entity @Audited public class Client implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Getter private Integer id; @Getter @Setter private String name; @Embedded @Getter @Setter private Address address; }
Address.java
@Embeddable @Getter @Setter @Audited public class Address implements Serializable { private int postalCode; private String address1; private String address2; }
ここでClientエンティティの履歴を管理したいのでクラスに対して@Auditedを付与します。
Clientエンティティの履歴を管理するエンティティは、デフォルトでClientAudとなります。Audは、Auditの略ですがわかりずらいのでClientHistoryにします。また、ClientHistoryのプロパティ名も、それぞれrevをauditId、revtypeをauditTypeに変更します。application.propertiesに次のように書けば変更できます。
spring.jpa.properties.org.hibernate.envers.audit_table_suffix=_history spring.jpa.properties.org.hibernate.envers.revision_field_name=audit_id spring.jpa.properties.org.hibernate.envers.revision_type_field_name=audit_type
テーブルを準備する
Flywayを使うので、sqlファイルに次のように書きます。
これで、Clientと対になるclientテーブル、ClientHistoryと対になるclient_historyテーブル、履歴管理テーブル全体の管理をするrevinfoが作られます。
create table client ( id integer not null auto_increment, name varchar(255), postal_code integer not null, address1 varchar(255), address2 varchar(255), primary key (id) ) engine=InnoDB; create table client_history ( id integer not null, name varchar(255), postal_code integer, address1 varchar(255), address2 varchar(255), audit_id integer not null, audit_type tinyint, primary key (id, audit_id) ) engine=InnoDB; create table revinfo ( rev integer not null auto_increment, revtstmp bigint, primary key (rev) ) engine=InnoDB; alter table client_history add constraint FK_client_history_revinfo foreign key (audit_id) references revinfo (rev);
できたテーブルがこれです。
登録、更新、削除やってみる
登録
Hibernate: insert into client (address1, address2, postal_code, name) values (?, ?, ?, ?) Hibernate: insert into revinfo (revtstmp) values (?) Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)
作られたデータ
"client" : { "id": 1, "name": "hey,inc", "postalCode": 1500011, "address1": "東京都", "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階" } "clientHistory": { "id": 1, "name": "hey,inc", "postalCode": 1500011, "address1": "東京都", "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階", "audit_id": 1, "audit_type": 0 } "revinfo": { "rev": 1, "revtstmp": 1528214457258 }
更新
Hibernate: update client set address1=?, address2=?, postal_code=?, name=? where id=? Hibernate: insert into revinfo (revtstmp) values (?) Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)
作られたデータ
"client" : { "id": 1, "name": "Coiney,inc", "postalCode": 1500011, "address1": "東京都", "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階" } "clientHistory": { "id": 1, "name": "Coiney,inc", "postalCode": 1500011, "address1": "東京都", "address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階", "audit_id": 2, "audit_type": 1 } "revinfo": { "rev": 2, "revtstmp": 1528214465266 }
削除
Hibernate: delete from client where id=? Hibernate: insert into revinfo (revtstmp) values (?) Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)
作られたデータ
"clientHistory": { "id": 1, "name": null, "postalCode": null, "address1": null, "address2": null, "audit_id": 3, "audit_type": 2 } "revinfo": { "rev": 3, "revtstmp": 1528214465681 }
ここでauditTypeの0、1、2は何ですか?という疑問が残ってるかと思いますが、それぞれ次の通りです。
0 → 登録
1 → 更新
2 → 削除
イベントにあったタイプが与えられます。
Hibernate Enversで履歴管理が楽になりました。現場からは以上でした。
私のPC事情
会社変わる時とかに、前の会社のPCでのブックマークとかアプリ移行できないときのためにメモ。
デスクトップアプリ
- Atom:テキストエディタ
- mi - テキストエディタ:テキストエディタ
- Boost Note | Boost Happiness, Productivity, and Creativity.:開発者のためのノートアプリ
- Clipy - Clipboard extension app for macOS:スニペット
- Franz – a free messaging app for Slack, Facebook Messenger, WhatsApp, Telegram and more:様々なコミュニケーションアプリを一元管理
- Free Git GUI Client - Windows, Mac, Linux | GitKraken:Git GUI
- Sourcetree - 無料の Git & Mercurial クライアント | Atlassian:Git GUI
- iTerm2 - macOS Terminal Replacement:ターミナル
- https://itunes.apple.com/jp/app/memory-clean-2-monitor-and-free-up-memory/id1114591412?mt=12:メモリの掃除
- Pingendo Bootstrap 4 builder:Bootstrap4.0のプロトタイプ作成
- Postico – a modern PostgreSQL client for the Mac:PostgresSqlのGUIツール
- PSequel, a PostgreSQL GUI Tool for macOS:PostgresSqlのGUIツール
- Sequel Pro:MySQLのGUIツール
- Spectacle:windowの移動とリサイズアプリ
- 「Table Tool」をMac App Storeで:CSV用エディタ
- Download Visual Studio Code - Mac, Linux, Windows:IDE
- Vivaldi: 広告ブロック, プライバシー保護, 設定豊富なブラウザ:ブラウザ
- Google Chrome ウェブブラウザ:ブラウザ
- Mac、PC、Linux 向け新高速ブラウザー | Firefox:ブラウザ
Chrome拡張機能
- Checker Plus for Gmail:Gメールの通知など
- Dimensions - Chrome ウェブストア:サイトの色々なサイズを測る
- EditThisCookie - Chrome ウェブストア:Cookieをいじいじ
- Elegantt | The leading Gantt Chart for Trello - Chrome ウェブストア:Trelloでガントチャート
- Full Page Screen Capture - Chrome ウェブストア:縦に長いページのスクリーンショットとるのに役立つ
- iKnow! ポップアップ辞書 - Chrome ウェブストア:ポインタを当てた英語の意味をポップアップで表示
- https://chrome.google.com/webstore/detail/insightio-for-github/pmhfgjjhhomfplgmbalncpcohgeijonh:GithubをIDEのように使える(まだうまく使えてない)
- JSON Viewer - Chrome ウェブストア:JSONのフォーマットを整えて表示
- LiveReload - Chrome ウェブストア:ホットリロード開発時に使える
- Octotree - Chrome ウェブストア:Githubでコードツリーが見れる
- Postman - Chrome ウェブストア:RESTの値見たい時とか
- Postman Interceptor - Chrome ウェブストア:Postmanで使う
- Screencastify - Screen Video Recorder - Chrome ウェブストア:デスクトップ上の動きの動画を撮れる
- Vue.js devtools - Chrome ウェブストア:Vue.jsのデバッグに役立つ
- Wappalyzer - Chrome ウェブストア:サイトで使ってるツールを表示
Web
- HTML Reference - A free guide to all HTML elements and attributes.
- Slides – Create and share presentations online
- Interneting Is Hard | Web Development Tutorials For Complete Beginners
- MythBusters JS
- DEVICON | All programming languages and development tools related icons font
- css-doodle
- Introduction · GitBook
- 無料CDNでWEB高速化 | Rapid START
- Find the latest versions of just about anything | Verstory
- もうプレゼン作成で迷わない!AIが資料レイアウト・配色を最適化する「Beautiful.AI」 | SELECK [セレック]
- Chibineko: The simplest test supporting tool
- THE TIMELINE | 無料年表作成サービス
- Miro: the collaborative whiteboard platform for distributed teams
- Feedly. Read more, know more.
- Witeboard:共有可能なホワイトボード
その他
- シェアデバッガー - Facebook for Developers:FB投稿用表示のデバッガ
- Login on Twitter:Twiiterカードのデバッガ