kitslog

何かを書く

Golang APIサーバのパッケージ(ディレクトリ)構成

こんにちは、@tkitsunaiです。

この記事を Replaceプロジェクトを終えて総括 Part0 - 89tech blog のPart1扱いにしちゃおうかと思ってます。

上記の記事にもあるようにAPIサーバをゼロから作る機会があり、APIサーバはFull Golangで書いたのでPackage構成などについて今後のためにも書き残しておこうと思います。

全体の実装パターンはClean Architectureを実装パターンとして採用しましたので、ある程度各layerを意識した名前にしています。

そして出来上がった構成はこんな感じです。

.
├── adapter
│   ├── controller
│   ├── gateway
│   ├── port
│   └── out
├── config
├── core
├── domain
│   ├── domain_name_a
│   ├── domain_name_b
│   └── ddd.go
├── infrastructure
│   ├── rest
│   └── driver
├── port
│   └── out
├── repository
├── testutil
└── usecase
    └── interactor

それぞれをClean Architectureでの4layerにわけてどんなものを置いているのか解説していきます。

Frameworks & Drivers layer

.
├── infrastructure
│   ├── rest
│   └── driver

RESTのためのHandler定義や、DB接続のためのDriverが置いてあります。frameworkにgin-gonicを採用したので、ginに依存したhandlerはここに置くことでそれ以下のlayerにginのルールを持ち込まず独立できています。

細かいhttp clientを拡張したものなどはpackageを作るか迷った挙句、infrastructure直下にgoファイルを置くような構成をとりました。

Adapter Interface / Usecase, middle layer

.
├── port
│   └── out
├── repository

Usecase layerとAdapter layerはDIPによって抽象化されているrepositoryやportに依存するため、interface定義をここに別packageとして置いています。

最初はusecase packageに入れていましたが、Adapter layerが参照しやすいように別に切り出すことにしました。usecaseやadapterのことも考えるinterfaceになるので実際に書くときはここからコーディングしていくことが多いです。

Adapter Interface layer

.
├── adapter
│   ├── controller
│   ├── gateway
│   ├── port
│   └── out

adapter内は何度も構成を変更しており、まだまだ試行錯誤中です。 controllerはhandlerから受け取るパラメタやRequestBodyをdomain layerに定義しているValue objectやEntityに変換する処理を担うコードを置いています。したがって、domainオブジェクトに変換すると同時にパラメタのValidationの役割も担います。

DIPによって抽象化されているrepositoryに依存しているため、gateway内にはrepositoryの実装コードを配置してます。

前は、gateway/persistence/rdbgateway/persistence/cacheなどを作っていましたが、全体の見通しが悪くなりつつあったので一旦フラットに置いてみて不都合が出てきたら変えようとしています。

portはpresenter的な役割を担うコードが中心に配置されてます。現時点ではinput-portは過剰だと考えoutput-portの実装しか置いていません。このoutput-portでdomainをjson変換するようなconverterの実装やメール通知やSlack通知をするための実装をしてます。

Usecase layer

.
└── usecase
    └── interactor

ここでもDIPによって抽象化されているrepositoryに依存させます。 主にControllerから呼ばれるUsecaseのInteractor実装をここに書いています。DDDの文脈でよくあるApplicationやServiceなどの名前があまり好きじゃないのでInteractorという名前は結構気に入っています。

ここでのコードをrepositoryとセットで最初に書くことが多いのと、ほぼ確実にinterfaceへの依存しかないのでテストもMockしやすくて一番好きなlayerです。ただそのときそのときのDDDやClean Architectureの理解度で書いていたこともあり、油断するとdomain logicがすぐにこのlayerに漏れ出ていることが多く、一番好きなlayerだけど何度も苦汁を飲まされているlayerです。

Entities(domain) layer

いつもdomain layerって呼んでいます。

.
├── domain
│   ├── domain_name_a
│   ├── domain_name_b
│   └── ddd.go

最初はdomain直下にdomain modelをそのまま置いていたのですが、集約の分け方やコンテキストによって変化する名前をつけるのにgolangだと単語_単語.goが一般的な命名規則になるので見通しが悪い状態になったりしたので、小さなbounded context単位でpackageを作るように階層を増やしました。

また、チーム内でdomainへの理解度に差があったりしたので、小手先ですがddd.goで簡単にentity, aggregate, valueobject なのかを識別させるように以下のようなコードを用意して自分が何を作っているのかの道しるべの手助けをしています。

type Entity interface {}
type Aggregation interface {}
type ValueObject interface {}

なんの実装も持たないinterfaceですが、使い方的には例としてuserというentity modelがあった場合には、interfaceの埋め込みして使います。

type User struct {
    domain.Entity
    id       Identifier
    name Name
}

最後に

Goでのプロジェクトを紹介するサンプルプロジェクトを参考にしながら、全体的にパッケージとして成熟させて今の形にまずは落ち着いています。今のところ大きな問題は起こってないはず...

まだまだ試行錯誤の段階で、パッケージ構成については正解があるものではないと思っています。ぜひTwitterなどで意見お待ちしております。

Replaceプロジェクトを終えて総括 Part0

こんにちは、@tkitsunaiです。はてなブログでは久々に書きます。気分も変えたくてテーマも変えました。

軽く自己紹介しておくと、普段はユーザベースという会社でSoftwareエンジニアしてます。

最近、ようやくプロジェクトが一区切りついて落ち着いてきたので色々思い出しつつ振り返ってみようかと思って書いています。普段アウトプットしてない弱小エンジニアなのでそろそろアウトプット増やしたい。

本当は11月ぐらいには終えて1人アドベントカレンダーでもやろうかと思ってましたが、いつの間にか12月の第1週も過ぎてました。どうみても私の怠慢です、本当にありがとうございました。

直近で携わっていたReplaceプロジェクトがどんなモノかと簡単に説明すると、SPEEDAというBtoBのサービスに関連する、社内で利用するCMSを作りました。どこまで書いていいのか分からないのであんまり詳細についてはここで書くかもしれないし書かないかもしれない。

気負っても絶対に全Partを記事に書き起こすことを出来ないことが自分でわかってるので、まずは今後書きたいなと思っている内容を気楽に整理するところから始めようと思います。こうやって予防線を貼ることで完遂させるつもりがないのかもしれないことについては否定できないのであえて触れないでおきます。

5分くらい考えてひとまず思いついたことを箇条書きにしただけなので、どの話題をどんなPartで整理するかはもちろん未定。

技術成分とポエム成分は7:3ぐらいで頑張りたいです。

さて、第一弾は何を書こうかな。

Go with Clean-Architecture


author: "@tkitsunai" date: 2018-05-21 next: /tutorials/github-pages-blog prev: /tutorials/automated-deployments title: Serverside Golang with Clean Architecture tags: ["golang","architecture"] categories : ["blog"]

description : About the Clean Architecture of software written in Go code.

ここ暫くのプロダクト開発ではGo + Clean Architectureを採択することが多くなりました。 何故この採択が増えたのか、Go + Clean Architectureを選ぶことについて改めて触れておくことにします。

まずはGolangとClean Architectureについて知らない人のためにほんの少しだけ説明します。

Go

Goの魅力は何でしょうか?

静的型付け、言語としてのシンプルさ、コンパイルの速度、並列プログラミング、GCアルゴリズムの最適化、実行バイナリ配布

何故Golangを選ぶのかを挙げるとしたら上記の理由が殆どになるでしょうが、それのどれらも本質的ではありません。

「そもそもGolangである必要はあるのか。」という質問において、答えはNOでもあるしYESでもあります。

言語毎のパラダイムの違いなどはあれど、信念を持って開発に臨むことでそういった問題はどれも些細な問題に成り下がるからです。

少し話が逸れました。今回はClean ArchitectureとGolangのお話をしますが、このコンテキストにおいて何故Golangを選択したのかについて説明するにはGolangの特性について軽く触れておく必要があります。

Golangのimport cycle

C++なども同じ言語仕様ですがGolangでは循環参照が禁止されています。

package aaa

import "bbb"

func A() {
    println("aaa.A()")
}
func CallB() {
    bar.B()
}
package bbb

import "aaa"

func B() {
    println("bbb.B()")
}
func CallA() {
    bar.A()
}

上記のようなパッケージ、aaaとbbbがお互いにimportしている状態ではコンパイルエラーになります。

今回のClean Architecuteにおいて、この循環参照の禁止という制約が良い働きをサポートします。

Clean Architecture

Clean ArchitectureがRobert Martinによって発表されてからはや6年ほどが経過しています。

参考:https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html

Clean Architectureとは考え方でありフレームワークです。

Domain Driven Designにおいてソフトウェアの複雑性と戦うために生み出されたレイヤードアーキテクチャの派生の一つです。

Clean Architectureの最も特徴的なことのひとつに、その各レイヤーの依存関係のルールが挙げられます。

CleanArchitecture

図にある通り、4つ(フレームワーク層→アダプター層→ユースケース層→ドメイン層)から構成されており、それぞれ円の中心にしか依存関係を保ってはいけないというルールがあります。

アプリケーションにおけるドメイン層が他に全く依存しないことで、フレームワークの書き方や本質的ではないアプリケーションロジックの影響を受けることを最小限に軽減します。

同じレイヤードアーキテクチャ仲間である、Hexagonal ArchitectureOnion Architectureも同じように依存関係に対してルールを持ちますが、Clean Architectureにおいては依存関係の方向性を単一方向にすることにより長けています。

Golang + Clean Architecture(CA)

Golangの特徴とCAの特徴を踏まえた上で、GolangでCAを採用すると、単方向を守りやすくなります。

以下は私のプロジェクトでよく採用するプロジェクトのファイル構成です。

Project Root
├── adapter
│   ├── controllers
│   │   └── user.go
│   ├── persistent
│   │   └── repository
│   │       └── user.go
│   └── presenters
│       └── user.go
├── domain
│   └── user.go
├── external
│   └── api
│       └── router.go
├── main.go
└── usecase
    ├── interactor
    │   └── user.go
    └── repository
        └── user.go

かなり省略しています。細かいところはプロジェクトによって変わりますが、そこまで大きくは変わりません。

上記のプロジェクトはUserのデータをCRUD出来るようなAPI Serverを想定します。

依存関係は、CAに則ると上記のフォルダ構成ではexternal→adapter→usecase→domainとなります。

各レイヤー毎を軽く見ていきます。

[external]

├── external
│   └── api
│       └── router.go

externalはフレームワークやWeb、Viewを位置する外界と繋ぐレイヤーです。 APIのルーティングなどはまさに外界と繋ぐ場所になるためexternalのレイヤーに配置します。

そしてrouter.goでは恐らく以下のようなコードブロックを書きます。(疑似コードです)

package api

import (
    "hoge/adapter/controllers"
    "hoge/adapter/presenters"
)

type Router struct {
    Router
}

func (r *Router) router() {

    r.GET("/user/:userId", func(c *Context) {
        result := controllers.User.GetUser(c.Param("userId"))
        presenters.User.Get(result)
    })

}

依存関係から整理すると、外界はAdapter層を利用(依存)することが出来ます。

上記コードではrouterがadapter層であるcontrollerとpresenterを利用しており、他に依存がありません。

routerはコントローラという名前の入り口と、それにパラメタを渡し、戻ってきた結果をそのままプレゼンターに横流しします。

関心の分離や単一責任の原則の観点で見ることも出来ますが、重要なのは依存しているレイヤー(import句)が"adapter"のみになっていることです。

external層はCAに則っていますね。では次のレイヤーを見ていきます。

[adapter]

├── adapter
│   ├── controllers
│   │   └── user.go
│   ├── persistent
│   │   └── repository
│   │       └── user.go
│   └── presenters
│       └── user.go

adapter層は名前の通り外界と繋ぐ役割を果たします。DBから値を取得するrepository実装やrouterからの入り口はここに実装されます。

ここでは、controllerについてだけ触れます。

package controllers

import (
    "hoge/usecase/interactor"
)

type User struct {
    Interactor interactor.User
}

func (u *User) Get(userId string) (*domain.User, error) {
    return u.Interactor.Get(userId)
}

Controllerの役割を省略してますが、コントローラでは本来入力値のチェックなども行いますが、基本的には呼ぶのはUsecase層のInteractorを呼ぶだけです。

ここでも依存関係は内側であるUsecase層に限定することが出来ています。 (戻り値であるdomainパッケージのuserについてはControllerが利用という意味で本質ではないので言及はしていません。)

次にUsecase層です。

[usecase]

└── usecase
    ├── interactor
    │   └── user.go
    └── repository
        └── user.go

RepositoryはAdapterレイヤーにあるRepository実装クラスの抽象です。CAの単方向のルールに則るためにはUsecase層はAdapter層に依存することは出来ないため、DIP(依存関係逆転の原則)を利用することで抽象に依存し、Usecase層は単方向を守ることが可能になっています。

大抵はRepositoryの実装クラスを外側、アプリケーションのエンドポイントに近い位置でDIするためUsecase層ではアプリケーションロジックに集中することが出来ます。

コードブロックは省きますが、ここではControllerから呼び出されるinteractorの処理の内、repository経由で取得したドメインオブジェクトやドメインオブジェクトを利用します。

[domain]

├── domain
│   └── user.go

Domain層は、円の中心に存在しアプリケーションの核となる部分です。ドメインドメインロジックを充実させます。

Domain層にはAtomicなコードを書いていくことを心がけ、シンプルで明瞭なコードにします。

よくDomainオブジェクトがデータベースのModel Entityと混在しているサンプルアプリを見ますが、クリーンアーキテクチャの思想に則った場合にはアンチパターンではないかと考えています。

(もちろん規模やケースバイケースにもよるとしか言えません)

Golangのimport cycleの働き

私のチームではじめてのClean Architectureを採用したプロジェクトでは、Clean Architectureを学習するために多くの時間を費やしました。

Golangでやることが決まり、Clean Architectureにしてみたいという状態から依存関係を守らせるのにimport cycleの仕組みが一役買っていました。

見るべきポイントとしては、

「今自分がどのレイヤーを書いているのか」

「円形の図を見た時、自分のレイヤーは外側に依存(import)していないか」

を頑なに繰り返すことでよりClean Architecuteの単一方向のルールの制約を実現させます。

まとめ

import cycleの制約をあえて活かすように設計することで依存関係の方向を纏めることができました。

Clean Architectureは簡易的なアプリケーションにおいてもコードベースがかなり増えるため、プロジェクトの規模などを適切に判断し、用法・用量を守って正しく使いましょう。

Hugo + Firebase + Circle CI Blog building

repost of articles previously hosted on hugo


author: "@tkitsunai" date: 2018-04-15 next: /tutorials/github-pages-blog prev: /tutorials/automated-deployments title: Hugo + Firebase + Circle CI Blog building tags: ["golang","hugo","firebase"] categories : ["blog"]

description : How to created this posts

Hugo + Firebase + Circle CI Blog building

Hi all, If you to start blogging, you do not need a web server.

You need is a Github account, a Google account, and challenging mind.

Static Site Generator

Dynamic sites are wonderful, but often bothering their heads.

I like programming, but blog is "simple is the best", and I think that display speed should be done more quickly. For that, I decided to use a static site generator.

Why Hugo?

Hexo and jekyll is often mentioned as a tool for static site generator, but I like golang. Mr.Gopher is cute. For gopher(you) like that, Hugo is recommended.

I Chose a simple theme for this blog. Design Theme => "hestia-pure"

Custom Theme

The this theme has favicon/author static image was fixed in the theme. so, was necessary to modify the layout file.

If you use from the theme list instead of the original your theme you should confirm that you can realize.

Theme is in GitHub, you can contribute with pull-request or fork it. I chose to clone and customize.

Working with Hugo

Install Hugo

If mac, you can install via homebrew.

$ brew install hugo

Hugo Commands

For the detailed explanation it is better to look at the Commands.

Most of the commands I use are as follows.

  • $ hugo new postdir/article_name
    • Create new content page
  • $ hugo
    • Generate static files (output dir is "public" folder default)
  • $ hugo server

Hugo has many features, but I think that it would be more happy to see the official website if necessary.

Hosting

This blog is hosted by hosting service firebase.

Why Firebase?

If you are intelligent you should know. Firebase is a competent hosting service and "Fastly" is used for CDN for asset delivery. (Of course I do not need a web server)

The features used in firebase.

  • Web site hosting.
    • Obviously...
  • Can be Custom domain connect.
    • If you want to use a custom domain, you can connect easily. (Consult with the DNS provider.)
  • Free SSL
    • You do not have to worry about SSL. firebase will do it automatically.
  • Easy deployment
    • If the site is ready, just run the deploy command.

Firebase provided cli tool. It can be easily installed via npm.

$ npm install -g firebase-tools

Deployment with Firebase-cli

The steps are simple, (if manual)

  1. Install firebase-cli.
  2. Log in to your Google Account with $ firebase login.
  3. Select the directory to be Public. (this is just a $ firebase init in the project directory)
  4. Let's $ firebase deploy, ...done!

Deployment with CircleCI

CircleCI is using it for blog post deployment.

When source code is pushed, it will be deployed to firebase.

I do not give detailed explanation on CircleCI.

example CircleCI's config.yml file.

version: 2
jobs:
  build:
    working_directory: ~/89tech
    machine: true
    steps:
      - checkout
      - run:
          name: install hugo
          command: go get -v github.com/gohugoio/hugo
      - run:
          name: run hugo
          command: hugo
      - run:
          name: Print the Current Time
          command: date
      - save_cache:
          key: buildend-{{ epoch }}
          paths:
            - ~/89tech

  deploy:
    working_directory: ~/89tech
    machine: true
    steps:
      - restore_cache:
          keys:
            - buildend
      - run:
          name: install firebase
          command: npm install -g firebase-tools
      - run:
          name: firebase deploy
          command: firebase --project "$FIREBASE_PROJECTID" deploy --token "$FIREBASE_TOKEN"

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

To execute firebase deploy with circle ci, you need to issue api token with

[~/89tech] (master) $ firebase login:ci

Visit this URL on any device to log in:
https://accounts.google.com/o/oauth2/auth?xxxxxxxxxx <- Google OAuth Link

Waiting for authentication...

✔  Success! Use this token to login on a CI server:

XYZxxxxxxxxxxxxxxxxx <- this is a firebase token

Example: firebase deploy --token "$FIREBASE_TOKEN"

$ firebase deploy --token XYZxxxxxxxxxxxxxxxxx

If firebase init is already completed, it is written in .firebaserc,

{
    "projects": {
        "default": "PROJECT_ID"
    }
}

Please set this PROJECT_ID and FIREBASE_TOKEN to Environment Variables of Circle CI.

f:id:kitslog:20200213234901p:plain
circleci

Future improvement points

In the above config.yml are using Circle CI's VM.

machine: true

In the setting above, since building time is long Because it is installing hugo every time it builds it is not a good practice.

Since version:2 of Circle CI can use docker image. I will change to config.yml.

See ya.

2017年締めくくり

2017年も終わるので、今年最後に今の想いとかはアウトプットしておこうと暑苦しいポエムを書くことにしました。

たぶん来年は絶対書かないだろうな。

2017年の流れ

公私ともに動きがあり躍動感のある密度の濃い一年。

プライベート

友達3人で一軒家シェアハウス開始

7月に10年以上付き合いのある新卒1社目の先輩、学生時代の同級生(全員一社目が同じ会社)でシェアハウスをはじめた。

お前ら友達居ないのかってぐらい週末に一人の家で溜まり場になってたのを面倒臭がったのがきっかけ。

プライベート的にはこの引越しが一番大きい事柄で不安要素も大きかったがビックリするぐらい大きな問題がない。

文句が出てもお互いに言い合うので上手くいってるんだろう。みんな適当だし。

誕生日会とか言って30近いおっさんどもが家でパーティ企画してキャッキャウフフしてる。正直気持ち悪い。

会社

自分はいまWebアプリケーションエンジニアとして働いている。まぁやることは当然Webだけじゃない。 結構ぶっちゃけてるところもあるけど、年末だしお酒も入ってるし許してほしい。

所属チームがSite Reliability Engineering(SRE)チームに。

もともと自分が所属してるチームでは本番バグのチケット消化や本番運用を行うチームだ。

このチームは端的に言うと「組織的には超重要で守りのチームだが、エンジニア的には面白くない環境」と捉えやすいチームだった。

業務の内容としてもテストコードもドキュメントもほとんど存在しない他人のコードをエラー内容から推測し、ビジネス側に仕様を確認し、エラーの内容を修正するというものだ。うむ、これだけ聞くとハズレくじのように見える。実際このチームで長続きしている人は一握りだし、才能溢れる血の気の多い人はどんどん別のチームになっていったりした。「他人のケツを拭くチーム」なんて揶揄されることもある。

こんな環境でも得られるスキルは当然ある。幅広くバカでかいシステムにあらゆる角度で触れていくのだから当然詳しくなる。

仕様を蓄積していくから頼られることが必然的に多くなって気持ちいい。仕事している感覚を得られる。(実際仕事はしてるんだけど)

一方で、目先のバグ対応だけを淡々とこなして達成感を得ても、プラスにはならない。マイナスをゼロに戻す作業だ。これを楽しいと思える人は正直稀だと思う。

自分の数年後のキャリアを考えたときに、単調で複雑な仕様を紐解く仕事をすることより、自分の創造性を発揮できる環境に身を置きたいと思うようになった。

自分の選択はチームを離脱することでもなく、チーム自身を変化させる戦略を選んだ。

そして今年の7月から、コツコツと水面下で施策を重ねてきたチームはアップサイドを狙えるSREチームに変化した。本当に念願の一歩だ。

当然、今までの差し込みの対応やバグチケット対応の機能は持ったままアップサイドの案件をやるから非常にチャレンジングな環境になり、新しい技術を盛り込んだプロジェクトも遂行する。

SREチームになったとき、元々存在していたインフラチームとの統合を行なったのでそれの変化にも追従しなければならなかった。 インフラエンジニア+ソフトウェアエンジニアというチームビルドを要するチーム基盤作り、新たなソフトウェアエンジニアの育成、新しい技術の習得、バグチケットの消化、社内インフラ。もうやることだらけ。

それでも、掴んだチャンスなので立ち止まらないように馴染ませていきたい。

来年またワクワクするようなことが待ち構えてるので、体を壊さない程度に頑張ろうと思う。

エンジニア側面の成長

チームがそんな動きの中で、チームや個人でもソフトウェアエンジニアとしての側面での成長もあった。

TDD、DDD、クリーンアーキテクチャ、CI/CD、ペアプロ/モブプロ、スクラム(カンバン)、新言語(Golang)、マイクロサービス、Dockernize

どれもこれも今までのチームでは導入し得なかった要素が盛りだくさんで、自分も皆も必死に足掻いている。

いきなりこれをやってすんなり行くとは思っていないし、当然反省点だらけだ。

それでも、諦めずに強い意志を持って続けることで周りは変化を受け入れてくれることも実感できた。


あまり締まらなかったが、たまにはこういうところにアウトプットするのも気持ちの整理になって良いですね。

それじゃあ、良いお年を。また来年。