More than 5 years have passed since last update.
なぜDeNA 20 新卒 Advent Calendar 2019を企画したのか【+専用アプリの解説】
はじめに
ついに最終日🎉
「なぜ内定者のカレンダーを企画したのか?」の理由と感想を書いていきます。
合わせて専用アプリの実装の解説も付けました🙌
また、この記事は@tocknとの合作記事です。
目次
| 章番号 | タイトル |
|---|---|
| 1章 | 内定者カレンダーを企画した理由 |
| 2章 | 全体の感想 |
| 3章 | 専用アプリ作成の発端 |
| 4章 | 専用アプリ作成のバックエンド実装 |
| 5章 | 専用アプリ作成のフロント実装 |
| 終章 | さいごに |
内定者カレンダーを企画した理由
「内定者で何かをしたい!」という思いと、「採用に貢献したい!」という思いが合わさった結果です。
この企画は来年で完成するもので、
- 👨🎓内定者のカレンダー
- 👩💻新卒のカレンダー
- 👨💻社員のカレンダー
の3つを作成することで
- 👨🎓内定者のカレンダー: どういう人が入社するのか
- 👩💻新卒のカレンダー: 1年後の成長度、成長環境
- 👨💻社員のカレンダー: 2年以降のキャリア
を一部ですが表現でき、DeNAという会社のことをより知ってもらえる機会になると考えました。
学生にとっては、どういう人が入社し、1年後の成長、その後のキャリアが見えるのは非常に有益な情報になると考えてます。
採用イベントで知ることが可能ですが記事という、より自然体で個人の思想がでる媒体での情報から感じ取れるものがあると私は思ってます。(就活中は各社の技術ブログはチェックしてました)
以上のような思いから企画をしました。
また、今回は「内定者vs社員!?」という企画をしたように、普通のカレンダーとは違う楽しみ方を提供できると考えています。
全体の感想
企画して本当によかったです。
内定者同士で交流が生まれたのは勿論ですが、「この子(サービス)知ってる!」となる機会も多く、同期をより知るきっかけになりました。
記事の中で研究内容や興味ある分野などを知ることもできて交流が増えました。
また、個人的にはイベントを企画する際のフローを今回で知れたのも良かったです。
申請などやり取りする中で、「弊社は頑張っている人を応援するとき、他の人を蹴落とすこと以外にNGを出すことはない」 と言っていただきましたが、本当にその通りだと感じました。
本当にいいカレンダーになったと思います。
申請など協力していただいた社員の方々、執筆者のみんな、読者の皆さん、本当に本当にありがとうございました。
専用アプリ作成の発端
vsの企画をしていた時にみんなに意見を募ったのが発端でした。
👁 スクリーンショット 2019-12-19 14.58.00.png
そして何気なく送信したメッセージが始まりでした...。
👁 スクリーンショット 2019-12-19 14.58.50.png
...3時間後にtocknがAPIを作りました...!?
👁 19-12-19-14-50-51-928_deco.jpg
APIができたことでハッカソンが突如始まりましたw
(だって、APIあったら作るしかないじゃん)
👁 19-12-19-14-51-25-135_deco.jpg
専用アプリ作成のバックエンド実装
バックエンド実装を担当した@tocknです。
naroのアイデアを見た時自分は地元のケンタッキーでお昼を食べていたのですが、すぐに実装したい衝動に駆られ、残っていたチキンフィレサンドを急いで口の中に突っ込みました。
そしてケンタッキーから家までの徒歩10分で、サーバーの構成を考えていました。
とりあえず自分はJSONを返すAPIだけ提供して、フロントは得意な人に実装してもらう形にして開発に柔軟性を持たせることにしました。
Qiitaには公式でAPIが提供されていますが、アドベントカレンダーに関するAPIは公開されていないようでした。案を見た感じ、アドベントカレンダーの内定者の総イイね数、社員の総イイね数をイイ感じに取得するAPIが必要になりそうです。そこで、カレンダーページをスクレイピングする事で総イイね数を取得する事にしました。
また、安く爆速で作るために慣れているGoを採用し、インフラはGAE, Datastoreを使う事に決めました。
(昨年度参加したDeNAのサマーインターンでGoを使ってスクレイピングを実装したのを思い出し、少しエモい気持ちになりました)
今回実装したコードはGitHubで公開しています。
ディレクトリ構成
.
├── Makefile
├── api
│ ├── handler
│ │ ├── handler.go
│ │ └── utils.go
│ └── router
│ └── router.go
├── appengine
│ ├── app.yaml
│ ├── cron.yaml
│ └── main.go
├── main.go
├── model
│ ├── article.go
│ ├── likes.go
│ └── repository
│ └── likes.go
├── persistence
│ ├── datastore
│ │ ├── article.go
│ │ └── likes.go
│ └── memory
│ ├── article.go
│ └── likes.go
└── qiita
└── qiita.go
シンプルな構成になっていると思います。層も少ないのでそれぞれ簡単に説明していきます。
model
レスポンスのモデルおよびデータベースのモデルを定義している層です。レスポンスもデータベースも同じ構造で対応しています。楽ですね。
type Likes struct {
_kind string `boom:"kind" json:"-"`
ID string `boom:"id" json:"-"`
Shinsotsu int64 `json:"shinsotsu"`
General int64 `json:"general"`
UpdatedAt time.Time `json:"updated_at"`
}
model/repository
データを永続化するためのインターフェースをここで定義しています。これによって永続化の実装を抽象化できるので、後述するサクッとローカルデバッグが実現できたりします。
type Likes interface {
GetNew() (*model.Likes, error)
Create(likes *model.Likes) error
}
qiita
QiitaのスクレイピングおよびAPIを叩いて値を取得する層です。以下は年、カレンダー名を引数に、そのカレンダーの総イイね数を返す関数です。
func GetAllLikes(year int64, title string) (int64, error) {
doc, err := getAdventDoc(year, title)
if err != nil {
return 0, err
}
selection := doc.Find("div.adventCalendarJumbotron_stats[title=Likes]")
likesStr := selection.Text()
likesStr = strings.TrimSpace(likesStr)
return strconv.ParseInt(likesStr, 10, 64)
}
api/handler
HTTPハンドラーをここで定義します。このHandler構造体はメンバにmodel/repositoryで定義したrepositoryインターフェースを持っており、New関数で生成します。インターフェースなので、Handler生成時に何を持たせるかで永続化の手法を変えることができます。(モックを持たせたり自由)
type Handler struct {
likesRepo repository.Likes
articleRepo repository.Article
}
func New(lr repository.Likes, as repository.Article) *Handler {
return &Handler{
likesRepo: lr,
articleRepo: as,
}
}
func (h *Handler) GetLikes(w http.ResponseWriter, r *http.Request) {
likes, err := h.likesRepo.GetNew()
if err != nil {
respondError(w, r, err, http.StatusInternalServerError, nil)
return
}
respondSuccess(w, r, http.StatusOK, likes)
}
persistence/datastore
GCPのDatastoreを使って永続化するための層です。model/repositoryの実装になっています。
type likesRepository struct {
client *boom.Boom
}
func NewLikesRepository(c *boom.Boom) repository.Likes {
return &likesRepository{
client: c,
}
}
func (r *likesRepository) GetNew() (*model.Likes, error) {
q := r.client.NewQuery("Likes").
Order("-UpdatedAt").
Limit(1)
var ls []*model.Likes
if _, err := r.client.GetAll(q, &ls); err != nil {
return nil, err
}
if len(ls) == 0 {
return nil, errors.New("not found")
}
return ls[0], nil
}
appengine/main
GAEで動かすためのmain関数です。ここでHandlerを生成し、Datastoreの実装を注入しています。
func main() {
ctx := context.Background()
if err := run(ctx); err != nil {
panic(err)
}
}
func run(ctx context.Context) error {
ds, err := clouddatastore.FromContext(ctx)
if err != nil {
return err
}
c := boom.FromClient(ctx, ds)
likeRepo := datastore.NewLikesRepository(c)
articleRepo := datastore.NewArticleRepository(c)
s := handler.New(likeRepo, articleRepo)
r := router.New(s)
http.Handle("/", r)
appengine.Main()
return nil
}
サクッとローカルデバッグ
APIとしてイイ感じに値を返せるか、手元でサクッとデバッグを行えるようにしたい。そこで活きてくるのがrepositoryによる永続化の抽象化です。今回persistence/memoryにも、model/repositoryの実装があります。これを使うと非常に簡単にデバッグできます。実装は以下の通りです。datastoreの実装ではclientをメンバに持っていましたが、memoryでは普通のmapを持っています。これを簡易DBとして扱います。
type likesRepository struct {
memory []*model.Likes
}
func NewLikesRepository() repository.Likes {
mem := make([]*model.Likes, 1)
mem[0] = &model.Likes{
Shinsotsu: 100,
General: 101,
UpdatedAt: time.Now(),
}
return &likesRepository{
memory: mem,
}
}
func (r *likesRepository) GetNew() (*model.Likes, error) {
return r.memory[len(r.memory)-1], nil
}
そして、ルートにあるmain.goが手元デバッグ用のmainです。
func main() {
if err := run(); err != nil {
panic(err)
}
}
func run() error {
likeRepo := memory.NewLikesRepository()
articleRepo := memory.NewArticleRepository()
s := handler.New(likeRepo, articleRepo)
r := router.New(s)
log.Println("serving...")
return http.ListenAndServe(":8080", r)
}
このようにmemoryの方を注入しているので、手元で簡単にデバッグができます!便利!(書こうと思えばテストも綺麗に書けますね🦁)
Cronで定期実行
リクエストのたびにQiitaをクロールしてはパフォーマンス悪いしQiitaにも申し訳ないので、Qiitaのクローリングは定期実行してGCPのDatastoreに保存します。
cron:
- description: "updatelikes"
url: /updatelikes
schedule: every 10 minutes
target: default
timezone: Asia/Tokyo
こんな感じで定義すれば、10分おきにクロールしてくれます。
以上、バックエンド実装のお話でした。
専用アプリ作成のフロント実装
フロントエンド実装を担当した@naro143です。
「毎日記事を見るきっかけになる」ということでアプリであることは必須だったのですが、いい機会なのでPWAを初めて触るNuxtで実装することにしました。
結構汚かったり無駄な実装があると思います。
是非アドバイスください👁 :pray:
本当に基礎的なことしかしてないので、基礎的でないところに絞って解説をします。
今回実装したコードはGitHubで公開しています。
アプリ紹介
サイトはこちら↓
https://vs-dena-advent-client.naro143.com/
nuxtの設定
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
