- 投稿日:2019-11-25T23:26:31+09:00
プライベートリポジトリをパスワードで限定公開するアプリを作った
GitHubのプライベートリポジトリを不特定多数に公開したい時ってありません? 私はあります、書類選考など。
不特定多数だから一人一人招待は無理だし、パスワード認証程度でいいんだけどなあと思いながら、
GitHubの設定でどうにかできないかと調べてみたんですが、ダメですね、なかったです。かなしいです。クソみたいなリポジトリなんてどうせ誰も見ないんだからパブリックにしちゃえば?と思われるかもしれません。
パブリックはなんか嫌だし、私が作ったものはクソじゃないし、百歩譲ってクソだったとしても自分のクソを全世界に公開する趣味はないです。なので作りました。他になさそうだったので。
http://35.233.244.144/
(クリックしても飛べない場合URLをコピーしてみてください)MineSweeper3Dのみパスワード公開してます。「unity」
これなに?
Webアプリケーションです。
使用技術は
フロント:React
サーバー:Golang echo
DB:Mongo
これはGCEのインスタンスの中のDockerで動いてます。
Webサービスではないので自分のリポジトリに適用させる場合はサーバー立ててCloneして云々が必要です。なぜWebサービスにしなかったのか
これはGitHubのアクセストークンを使用し、プライベートリポジトリを取得してきています。
悪意のある運営者であれば、サービスを使用した人のプライベートリポジトリを取得できます。
それって気持ち悪くない? 私だったら利用しません。なのでWebサービスにはしませんでした。どのように動いているのか
GitHub APIではアクセストークンを使用してリポジトリのファイルを取得できます。トークンは生成する必要があります。
https://api.github.com/repos/ユーザー名/リポジトリ/contents?access_token=トークン
みたいな。最初のディレクトリしか出ないので全て取得するにはディレクトリかを判別して再帰的に繰り返す必要があります。サーバーを立てる前にGolangで対象のリポジトリをクローリングするところから始まります。
./repo -repo yourrepo -expire 2020-02-02 -password yourpassword
こんなコマンドをサーバー内で実行します。
これはプロジェクトのファイル一覧を再帰的に取得してDBに入れています。あとはサーバーを立ててパスワード認証をして、認証に成功したらファイルを表示させてます。
ファイルは逐一GitHub APIを介してます。
用途としては、プライベートリポジトリの完全なミラーが目的ではなく、パスワード認証して限定公開が主目的だからです。
コマンド実行時のディレクトリ構造を保存し、そのパスをもとにファイルにアクセスしています。全体的に難しいことはしてません。
まとめ
本当に技術的に気になりましたらソースを見てください。
私のようにプライベートリポジトリを限定公開したいという人がいましたら、これを使うなり、使わなくともこれが参考になればと思い、記事をしたためました。
調べても同様のものが見つからなくて絶望した過去の私のような人間が一人でも救われるのであれば幸いです。
- 投稿日:2019-11-25T21:59:30+09:00
【Go】日時を文字列に変換する時によく使うフォーマット指定
はじめに
しょっちゅう「あれ?Golangで日付を文字列に変換する時のフォーマットってどんなんだっけ?」となって毎回調べているので、個人的によく使うやつを自分用のメモとして残しておきます。
フォーマットの詳しい指定方法については以下の記事がわかりやすくまとまっています。
本記事では細かい解説はしません。よく使うフォーマット
年月日時分秒の指定方法さえ覚えておけば大体なんとかなると思います。
日時形式 フォーマット yyyy-mm-dd 2006-01-02 yyyy-m-d 2006-1-2 HH:MM:SS 03:04:05 H:M:S 3:4:5 yyyy-mm-dd HH:MM:SS 2006-01-02 03:04:05 サンプルコード
package main import ( "time" ) func main() { t := time.Date(2019, time.February, 9, 8, 7, 4, 0, time.UTC) // => 2019-02-09 08:07:04 +0000 UTC /* * 日付 */ // yyyy-mm-dd t.Format("2006-01-02") // => "2019-02-09" // yyyy-m-d t.Format("2006-1-2") // => "2019-2-9" /* * 時間 */ // HH:MM:SS t.Format("03:04:05") // => "08:07:04" // H:M:S t.Format("3:4:5") // => "8:7:4" /* * 日付 + 時間 */ // yyyy-mm-dd HH:MM:SS t.Format("2006-01-02 03:04:05") // => "2019-02-09 08:07:04" }おわりに
改めてちゃんと調べてみて気づきましたが、月日時分秒の順番で12345になってるんですね。
そう考えると簡単に覚えられるな。。。まとめた意味なかったかも。。。参考
- 投稿日:2019-11-25T19:42:46+09:00
リリースまで至らなかった個人開発サービスのコードを全公開して反省してみる【Nuxt + Go】
これはなに
これはDeNA20卒内定者エンジニアによるアドベントカレンダーDeNA 20 新卒 Advent Calendar 2019の記事として書かれています。
はじめに
僕は趣味の一環でWebサービスを作ったり作ろうとしたりしています。ちょうど一年程前に企画・開発を始めたのですが、リリースまで至らなかったサービスがあったことを思い出したのでこれを機にコードを全公開して振り返ってみることにします。
公開したコードはこちらになります。
https://github.com/tockn/emukone_public自分で実装しておきながらすごく無責任なのですが、正直どういう思想で実装していたのかその詳細はもう忘れてしまっているので、コードを読んで思い出しながら書く形になります。
なぜリリースしなかったのか?
まずはこれです。当時DDDやクリーンアーキテクチャといったソフトウェアアーキテクチャに興味があり、どうせ実装するなら試したことない構成で色々やってみようとしていました。しかしこれが罠で、要件に合っていないオレオレな酷い構成で作り始めてしまい、実装すればするほどモチベーションが下がり、最終的に死んでしまいました。悲しいですね。
どんなサービスだったのか?
ズバリ、「アーティストとライブハウスをマッチングをするサービス」です。高校時代バンド活動をしていた経験から思いつきました。お金儲けだけを中心に考えている質の低いイベントを淘汰し、本当に良い時間・空間を作り上げようと真剣になっている素晴らしいイベントが生き残る世界を実現しようとしたサービスでした。
ざっくり使用技術とか
フロントエンドはNuxt.jsで、バックエンドはGoのよくある構成です。そもそもローカルで開発したまま死んだのでインフラまでは考えてません。
UIフレームワークとして一部SemanticUIを使いました。
データベースとしてMySQLを、マイグレーションにはsql-migrateを使いました。
ローカル開発環境にdocker-composeを使っていました。
CircleCIを使ったCI/CD環境も一応整えていた気がします。DB設計
MySQLWorkbenchでモデリングしたものがまだ残っていたので貼っておきます。
ユーザーが、アーティストである「アーティストユーザー」と、イベント主催専用ユーザーの「ブッカーユーザー」に別れていたので、それに合わせた設計になっています。
このDB設計は非常に苦しんだ覚えがあります。READMEにこんな一言が添えられていました。。。(どうせ個人開発なので問題ないけど。)
フロントエンド(Nuxt.js)
動作
どんな感じの見た目で動作をするのか紹介してみます。例としてアーティストユーザーのプロフィールの編集画面を晒します。
@TOS pic.twitter.com/Wd3PgLVXTg
— tockn (@Tockn_inthebox) November 25, 2019Youtubeのリンクを貼ると埋め込み形式に変換してくれる機能なんかも作りました。
構成
. ├── assets ├── components │ ├── atoms │ │ └── AtomButton.vue │ ├── molecules │ │ └── MolContentCard.vue │ ├── organisms │ │ └── OrgProfileHeader.vue │ └── pages │ └── PageArtistProfile.vue ├── layouts ├── middleware ├── node_modules ├── pages ├── plugins ├── static └── storeAtomic Designの概念を取り入れたコンポーネント構成になっています。
コンポーネントとして切り出す作業ですが、初めから部品を切り出して組み合わせるのではなく、まず初めに一つのコンポーネントでページ一枚を作りました。
そしてそのページを実装していく上で再利用することになった(なりそう)な部品を切り出していくことで、最終的にAtoms, Molecules, Organismsとして構成していきました。Atomsはその名の通り原子のようなそれ以上分解できないコンポーネントとして、Moleculesは複数のAtomsを組み合わせることで実装されるよう分解していきました。
Atoms, Moleculesはサービスのドメイン情報を一切含めないようにし、他サービスでも利用できるような疎結合なものにすることを心がけるようにしました。
そして、OrganismではAtoms、Moleculesを利用してドメイン情報の入った再利用するコンポーネントとして構成しました。図示すると以下のような感じです。
サーバーサイド(Go)
構成
オレオレなレイヤードアーキテクチャになっています。
. ├── docs │ └── swagger ├── domain │ ├── entity │ ├── repository │ └── service ├── infrastructure │ └── repository │ ├── mysql │ └── s3 ├── interface │ └── web │ ├── handler │ ├── middleware │ └── router ├── migrations │ └── mysql ├── registory └── usecase図示すると以下のような形です。
構成についての感想
正直、これがとにかく失敗でした。結局今回の実装の大部分って普通のCRUDなんですよねー。それをこんな形で過度にレイヤ化したり抽象化していってしまうと、普通のCRUDの実装に膨大な時間がかかってしまいます。当たり前ですが。
テストコードも殆ど書けていないので、レイヤ化の意味が殆どありません。
おまけに
domain/entity
のstructにjsonタグを書いてしまっているので、レイヤードにすら作れていません。APIレスポンスの形式を変更するためにドメインエンティティーを触らなければいけないのは、レイヤードアーキテクチャの意味を成してないですよね。domain/entity/user.gotype User struct { ID string `json:"id"` Name string `json:"name"` IconURL string `json:"icon_url"` Identifier string `json:"identifier"` HeaderImageURL string `json:"header_image_url"` MetaDescription string `json:"meta_description"` Tags []*UserTag `json:"tags"` Locations []*Location `json:"locations"` WebsiteURLs []*WebsiteURL `json:"website_urls"` UserImages []*UserImage `json:"user_images"` }とは言っても、これでAPIレスポンスのためのstructを別のレイヤに生やすと、
entity.User
とmysql.User
間に加えてさらにresponse.User
間を変換するマッパーのコードを書くことになるので、しんどさが増していたと思います。
実装の大部分が普通のCRUDなのにこんな構成にしてしまうのは、失敗以外の何物でもないです。これなら他の言語やフレームワークを使ってMVCだけで作った方が断然良いです。swaggo
interface/web/handler
を覗くとわかるのですが、swaggoを使って自動でswaggerの生成なんかもやっていました。以下のようにコメントで書くだけでswaggerを自動生成してくれます。便利〜interface/web/handler/user.go// GetUserMeta godoc // @Summary ユーザーのメタ情報(概略)を取得する // @Description // @Accept json // @Produce json // @Param userID path string true "user id" // @Success 200 {object} entity.UserMeta // @Router /users/{userID}/meta [get] func (h *user) GetUserMeta(c *gin.Context) { id := c.Param("userID") du, err := h.usecase.ShowMeta(id) if err != nil { ErrorResponse(c, err) return } c.JSON(http.StatusOK, du) }開発を振り返って
コードを読んでその時考えていたことを思い出しながら記事を書いていましたが、今なら絶対やらないような事をたくさんしていて恥ずかしくなりました。
振り返れば、個人でパブリックにリリースして使ってもらったサービスは一つだけで、しかも初めてWebプログラミングを学んだ勢いで作りリリースしたものでした。
それ以降様々な概念を学び、より良いコードを書けるように、より良い設計ができるように励んできましたが、逆にその知識が足枷となり「こんなコードじゃダメだ」「こんなアーキテクチャはクソだ」と実力が理想を上まらず、落ち込み、結果モチベーションがなくなりリリースできない。。。その繰り返しでした。ではなぜ初めてのサービスはリリースできたのか?と考えてみると、自分の技術に対して一切期待をしていなかったからだと思います。初めてだし仕方ないや〜の精神を貫いた結果、爆速実装と爆速リリースができました。「動いてるからヨシッ!」を肯定するわけではないのですが、結局どんなに素晴らしいコードで、アーキテクチャで、思想を持って作っていたとしても、リリースしてユーザーに触ってもらわない限りは何の意味もありません。
Done is better than perfect
本当に良い言葉だと思います。
新しく何かを作り始めるときは、とにかく「作りきる事」を第一に考えて手を動かしていこうと心に刻みました。
- 投稿日:2019-11-25T17:41:20+09:00
【Go】SpaceCloudでMySQL/GraphQLサーバを立ち上げる
本記事で触れないこと
- GraphQLに関する解説
- Rest APIとの比較に関して
※RestAPI GraphQL 比較
等のキーワードでggると、参考になる記事がヒットすると思いますSpace Cloudについて
- フロントエンドから
GraphQL
(またはREST API
)を呼び出すためのAPIサーバ- MySQL + GraphQLサーバを立ち上げる場合の有力候補
※ prismaもかなり有名(比較/検証予定)
※ Postgressの場合はhasura/graphql-engineが人気みたい- Goで書かれており、クラウドネイティブのルールに従って水平方向にスケーリングしてくれるらしい
対応DB
- MongoDB
- PostgreSQL、およびPostgreSQL互換のデータベース(CockroachDB、Yugabyteなど)
- MySQLおよびMySQL互換データベース(TiDB、MariaDBなど)
事前準備
Docker環境の用意
- 公式のQuick startに従い、docker-composeで構築します
- MySQL5.7バージョンを明示的に指定 + my.cnfの設定を上書き + DBデータの永続化を設定しています
docker-compose.yamlversion: "3.7" services: space-cloud: image: spaceuptech/space-cloud ports: - "4122:4122" - "4126:4126" links: - testdb restart: always environment: ## The DEV environment lets you skip login in Mission Control DEV: "true" ## Change the login credentials of Mission Control (Admin UI) # ADMIN_USER: "admin" # ADMIN_PASS: "123" # ADMIN_SECRET: "some-secret" # This is the JWT secret used for login authentication in Mission Control testdb: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: testdb TZ: 'Asia/Tokyo' command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci volumes: - ./testdb/data:/var/lib/mysql - ./testdb/my.cnf:/etc/mysql/conf.d/my.cnf - ./testdb/sql:/docker-entrypoint-initdb.d ports: - 3306:3306mysql/my.cnf[mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [client] default-character-set=utf8mb4DBスキーマの作成
以下の要領でMySQLのスキーマを作成しておく
(docker-composeで作成したMySQLコンテナにログイン + MySQLに接続 + スキーマ作成)$ docker-compose exec testdb bash root@04ee656af514:/# mysql -u root -p root mysql> create database testdb; Query OK, 1 row affected (0.01 sec) mysql> exitSpace Cloud操作
プロジェクト作成
任意のプロジェクト名を入力し、MSQLの項目を選択
※ キャプチャを撮り忘れたのでこちらから転載しています
(実際の操作ではMYSQL
を選択しています)DB接続設定
メニューの
Database
からEdit Connection
を選択し、ダイアログ内にroot:root@tcp(testdb:3306)/
を入力
※ こちらもキャプチャを忘れたので、先ほどと同じく転載画像ですテーブル作成
Add a table
ボタンから、任意のテーブル名+テーブル定義を記載する
create users table
type users { id: ID! @primary phone: String! }create post table
後のselect joinクエリのサンプルで利用したいので、もう一つテーブルを作っておく
先ほど作成したusersテーブルを「外部キー参照」したい場合は、以下のように@relation
ディレクティブを指定するtype post { id: ID! @primary title: String! users_id: users @relation }クエリ発行
INSERT
mutation { insert_users( docs: [ { id: "1", phone: "09011112222"}, { id: "2", phone: "09033334444"} ] ) @mysql { status } }mutation { insert_post( docs: [ { id: "1", users_id: "1", title: "test_title1" }, { id: "2", users_id: "1", title: "test_title2" }, { id: "3", users_id: "2", title: "test_title3" }, { id: "4", users_id: "2", title: "test_title4" } ] ) @mysql { status } }SELECT
単一テーブルのSELECT
query { users ( where: {id: "1"} ) @mysql { id phone } }複数テーブルのJOINクエリのサンプル
query { users @mysql { id phone post( where: {users_id: "users.id"} ) @mysql { title } } }UPDATE
mutation { update_users ( where: { id: "2"}, set: {phone: "08077778888"} ) @mysql { status } }課題
クエリで日本語等のマルチバイト文字を送信した際に、
Network error: Failed to fetch
のエラーが出てしまったので、mysql/my.cnf
で文字コードの設定などチューニングの必要がありそう参考
- 投稿日:2019-11-25T17:26:39+09:00
nuxtで作った静的ファイルをgolangのechoでホスティングする。
背景
機能の内容は言えませんが、外部APIを利用した既存の機能の簡易版のツールを作りたい。そういった依頼が舞い込んできました。
すでにその機能は提供されていますが、画面仕様のもと多くの項目の入力が必要で、客先でさくっと処理させるには不向きでした。
そのため、既存機能の登録機能のみを切り出してほしいとのことでした。
二つ返事で了承。
今後のことを考えフロントエンドは別プロジェクトにし、APIは既存のものを利用。
外部のAPIはフロントエンドでのスクリプト読み込み後のAjax送信とサーバーサイドでのポストの処理があり、特定のホスト名と特定のIPにしか対応してませんでした。
それは困った。
ということで、nuxtで静的サイトとしてgenerateしたものを既存のプロジェクトでホスティングさせてしまえば、ホスト名とIPの課題は解決するということでやり方を書いておきます。※ソースは抜粋しか載せられないのでその点はご理解ください。
流れ
- nuxtでプロジェクトを作成しgenerateすることで静的サイトへ
- golangとechoでできた既存システムでgenerateした静的サイトをホスティング
- リリース方法を検討していい感じにリリースできるようにする。
nuxtでプロジェクトを作成しgenerateすることで静的サイトへ
- ソースを載せられないのでいい感じにご自身で実装してください。
静的サイトへgenerate
- なんのこっちゃない。あまりやったことないがとりあえずlocalで
yarn generate
- generate?めんどくさいのでpackage.jsonをいじって
package.json"gen": "nuxt generate",yarn gen
- distに静的なファイルが出力されました。 静的なファイルっていうくらいなので、要はHTMLを生で書いたのと同じ状態ですよね?ということで、ブラウザでこのファイルを開く
- おや?スクリプト(画面のイベント)が発火してない?そんなことある? どうやらnuxt generateしても、ルート情報が正しくないと、生成されたスクリプトを読み込めないらしい。 この場合だと当然、/hoge/hoge/index.htmlでアクセスしてる。少し調べるとわかりますが、どうやらgithubpageやnetlifyやS3でホスティングしないと正常に動かないらしい。 なるほど。
じゃあ、当社で使っているbacklogで「ブラウザで直接開く」なるものがあるので、まずはそいつでホスティングさせてみましょう。
backlog「ブラウザで直接開く」で開いても正しく動かない。
なるほど。URL見ればわかるが、これはHTMLをただブラウザで開いてるだけか。。。ホスティングサービスとは別物。。。
gitと名乗る以上極力githubに寄せてほしいところだが、できないものはできないのでどうしようもない。じゃあ、githubでpage作るか。
はい、そうです。金払わんとできない。金はない。やめる。じゃあ、いったんnetlifyで。
いける!どうやらホスティングさせてあげれば正しく動くみたい。ということが分かったので、nuxtはこんなところにしておく。ホスティングする際のエントリーポイントをルートに追加
http://hostname/nuxtprojectで受ける予定なので、nuxt.config.jsに以下を追加router: { base: '/nuxtproject' }golangとechoでできた既存システムでgenerateした静的サイトをホスティング
- まずはgitから落としてプロジェクトへぶっこむ
cd echoproject git clone <nuxtprojectURL>
echoのプロジェクト直下にぶっこむ。
depやglideで入れたかったのですが、なかなかてこずってできなかったので、直接ぶっこむ。
- 次にgenerateする
cd nuxtproject yarn gen
後で、distもリポジトリにぶっこむつくりにしますが、いったんはこんな感じでOK.
- ホスティングする
非常に簡単。やっぱりFWって素晴らしい。
e.Static("/nuxtproject", "nuxtproject/dist")これだけでOK。
- nuxtprojectを編集した際は毎回cloneしてgenerateする必要があるよ。
こっからいろいろ考える。
- パッケージ化しようとしたけど、うまくできなかったので、以下の流れで本番のモジュールに組み込む
- distをリポジトリ保存する。
- echoprojectのデプロイ時にcloneする。
- 環境により変更すべき値を考慮する。
distをリポジトリ保存する。
.gitignoreでdistを外し、yarn generateした後にpushする。
さすがに毎回手でやるのはだるい。本当ならビルド環境用意してそいつにやらせてあげればいいのだけれども、サーバー用意するのがめんどいうえ、金がない。
なのでローカルで必ずやるようにする。どうやらgitの機能にHookがあるらしい。
コミット前、プッシュ前様々なタイミングでフックができるらしいが、今回はコミットする前を選択。
.git/hooks/pre-commitにコマンド書いとけばいいらしい。#!/bin/sh yarn yarn gen git add . exit 0
- インストールして静的なファイルを生成、できたファイルを管理対象にして終わり。pushは自分でやる これで常にdistが最新される。
echoprojectのデプロイ時にcloneする。
本来ならここでビルドすればよいのだが、dokerのイメージにnuxtとnodeとnpmとyarnを入れるのは避けたかったので、クローンするだけ。その際今回のnuxtprojectはパブリックなリポジトリではないので、いじいじする。
gitはnetrcの認証情報をもとにリポジトリからとってくることができるらしい。じゃあ簡単だ。
echoprojectにechoproject_nutrcをまずは作ります。# 自分の情報を設定してね。 machine <git_host> login <git_user_id> password <git_user_pwd>これをdockerfileで ~/.netrcにしてやればOK
RUN cp echoproject_nutrc ~/.netrc環境により変更すべき値を考慮する。
当然開発環境と本番環境は分かれてまっせということ。
echoprojectではENVという環境変数で振り分けてますが、nuxtはどうしましょう。
よし、どちらもgenerateしてホスティングするdistの名称を変えればOKじゃね?ということでそのようにします。
nuxt.config-dev.jsを作る
nuxt.config-dev.jsでgenerateの際の出力フォルダを指定
generate: { subFolders: false, dir: './dist_dev' },
- package.jsonに下記を追加
"gen-dev":"nuxt generate --config-file nuxt.config-dev.js",.git/hooks/pre-commitにyarn gen-devを追加
これでdistとdist_devができるので後はechoprojectでdistをホスティングするのかdist_devをホスティングするの指定してあげればOK。
非常にむりやりではありますが、今回はこのようにしましたとさ。
- 投稿日:2019-11-25T14:29:06+09:00
Golangで「特定のpackage以外の全てのpackageに対してtest」を行う方法
TL;DR
go test `go list ./... | grep -v 除外したいパッケージへのpath`解説
go test
の後ろにはテストしたいパッケージ名を書いていく。(全実行したい場合はgo test ./...
)go test internal/hoge internal/fuga
go list を実行すると次のようにパッケージのリストを出力してくれるので
github.com/seya/test/server/internal/hoge github.com/seya/test/server/internal/fuga除外して欲しいものをgrepで除外して渡してあげればok
- 投稿日:2019-11-25T11:39:45+09:00
golangの数値型とmysqlの整数型
TL;DR
unsignedだったらunsignedで合わせる。
not unsignedだったらnot unsignedで合わせる。
アンマッチの場合はきっちりオーバーフローするので回避したい。
intは甘え。mysqlの整数型
リンク先参照
https://dev.mysql.com/doc/refman/5.7/en/numeric-type-overview.htmlgolangの整数型
リンク先参照
http://golang.jp/go_spec#Numeric_typesざっくり対応
mysql golang tiny int int8 int int32 big int int64 unsignedはunsigned同士で対応。
- 投稿日:2019-11-25T11:24:17+09:00
Golangのテンプレートエンジンを使い、日付・時刻のフォーマット指定
はじめに
Golangのテンプレートにおいて、日付のフォーマットを指定する方法。
方法
Golangのテンプレートでは、
{{ .変数名 }}
のような形で、テンプレートに渡されたデータにアクセスすることができる。
たとえば、time型の変数名: now
が渡されたと仮定すると、以下のようにフォーマットを指定することができる。
(何も指定しないと、2009-11-10 23:00:00 +0000 UTCのような形式となる。)・YYYY/MM/DD形式で指定したい場合
.now.Format "2006/01/02"
・YYYY年MM月DD日 hh時mm分ss秒形式で指定したい場合
.now.Format "2006年01月02日 15時04分05秒"
のように記載すればよい。
まとめ
つまり、.Format "~"を用いると日付・時刻を指定可能である。
- 投稿日:2019-11-25T11:13:48+09:00
Sliceの値を構造体に表す方法
初めまして、初投稿となります。
最近Goを初めて約2ヶ月なのですが、まだまだ慣れない
特に配列が苦手だった私にとっては「arrayとsliceってなんぞや?」で
3日間苦しむことも…(´・ω・`)マジツレェなぜsliceと構造体を使おうとしたか?
開発している時に複数の値を入力して、
Googleのdatastoreに構造体の値を出力させるものを作ろうとしていました。いざsliceを使って何個も値を入力できるようにしたのです。
「よしできた!( ・∇・)」と思ってdatastore見たら
構造体の一箇所にsliceのデータがギッシリ詰まってましたΣ(・□・;)[{1 1}{2 4}{3 9}{4 16}{5 25}]みたいな感じで
もう摩訶不思議アドベンチャーです
こういうので引っかかったのは私だけじゃないはずと思いたい…結果的にどうしたか
一度作ったsliceを別の構造体に挿入してみたって感じです
まずは簡単に構造体とsliceを作ってみますpackage main import ( "fmt" ) type Slice struct { Singular int //単数 Power int //累乗 } type Store struct { //sliceの値を入れる構造体 Singular_answer int Power_answer int } func main() { slice := []Slice{} //構造体Sliceをsliceにします for i := 1; i <= 5; i++ { singular := i power := i * i fmt.Println(singular, power) /* 1 1 2 4 3 9 4 16 5 25 */ //appendでsliceの中にsinfularとpowerの値がそれぞれ入る slice = append(slice, Slice{ Singular: singular, Power: power, }) } fmt.Println(slice) //[{1 1} {2 4} {3 9} {4 16} {5 25}]と値が入っていることが確認できた!とりあえずこれで構造体からsliceにできましたね(´・ω・`)フゥ…
しかしこれでgoonを使って書き込みしたら私みたいになりますじゃあどうするべきか
//ここからが解決策 //rangeでsliceに入っている値の数ループさせます for _, input := range slice { slice_input := Slice{ Singular: input.Singular, Power: input.Power, } fmt.Println(slice_input) /* {1 1} {2 4} {3 9} {4 16} {5 25} */ } }このようにあらかじめ用意しておいた構造体に
もう一度突っ込んで上げることでスッキリしました…datastoreに突っ込む時はgoon.Putをループに突っ込んで上げると
一回のループで一個ずつ入ってくれるはず(´・ω・`)以上!!
- 投稿日:2019-11-25T10:42:30+09:00
protobufのenumをgolangの型(int32)に変換する
protobuf で定義した enum を golang handler 内で int32型 に変換したかった時
「protobuf enum int 変換」みたいなワードでササっとググって出てこなかったのでメモとして残しておきます。結論としては、単純に変換したい型で囲ってあげればよい。
type(enum type)
[protobuf] service User { rpc PostUser(PostUserRequest) returns (User) {} } enum SexType { OTHER = 0; MALE = 1; FEMALE = 2; } message User { int64 id = 1; string name = 2; SexType sex = 3; } message PostUserRequest{ string name = 1; SexType sex = 2; }[golang handler] func GetUser(ctx context.Context, req *user.PostUserRequest) (*user.User, error) { var sex int32 sex = int32(req.SexType) ... something ... }
- 投稿日:2019-11-25T08:17:49+09:00
[Golang]構造体でフィールドの初期値をnilにするには、そのフィールドをポインタ型にすれば良い件
はじめに
golangでよく使う構造体ですが、特定のフィールドの初期値を0やfalseではなく、nilにしたい場合ってありませんか?
ポインタ型で指定することで、簡単にできるので共有しておきます。いきなり結論のコード
package main import "fmt" type a struct { valueA int } type b struct { valueB *int } func main(){ var sa a var sb b fmt.Println(sa,sb) // => {0} {<nil>} }解説
1つ目の構造体では普通に
int
型を指定、2つ目の構造体ではint
のポインタ型を指定しています。それぞれの構造体type a struct { valueA int } type b struct { valueB *int }下の通り、宣言だけしてそのまま実行すると、初期値が出力されます。
初期値を指定var sa a var sb b fmt.Println(sa,sb) // => {0} {<nil>}出力結果が、2つの構造体で違います。
1つ目の構造体は、初期値が0なのに対して、2つ目の構造体は初期値がnilになっています。使用例
もしかしたら、あまり違いがないと感じられる方もいるかもしれませんが、実はこれはとても大きく違います。
たとえば、以下のような構造体で、User
の値を管理したい場合。使用例:うまくいかないパターンtype User struct { lastname string firstname string age uint64 }
lastname
は「鈴木」、firstname
は「太郎」、age
はいったん空けておきたい…なんて場合があったとします。使用例:User情報を入力var u User u.lastname = "鈴木" u.lastname = "太郎"実はこれで実行すると、
u.age
にはint型の初期値である0が入ってしまいます。
こうなると、このユーザーが本当に0歳のなのか、それとも年齢が分からないので空けていたユーザーなのかが、わかりません。
これはまずい。こんなときに、
使用例:うまくいくパターンtype User struct { lastname string firstname string age *uint64 }このようにポインタ型で指定してあげると、
u.age
には初期値nil
が入るので、とても便利。
0歳か不明なのか分からない…といったことが無くなります。年齢が不明のときの構造体&User{ lastname : "鈴木", firstname: "太郎", age : nil, }0歳のときの構造体&User{ lastname : "鈴木", firstname: "太郎", age : 0, }bool型もnilにできる
ちなみに、フラグとして使うことも多いbool型でも、初期値をfalseにすることなく使えたりします。
試してみて下さい。さいごに
最後まで読んで頂いてありがとうございます。
今日は、構造体の初期値をnilにする方法について、共有致しました。
また、不備があればコメント頂けると嬉しいです。
- 投稿日:2019-11-25T03:57:06+09:00
io.Readerをすこれ
0. なんで io.Reader?
去年のアドベントカレンダーではGo4までだったのが今年はなんとGo7までできており、Goへの関心が高まっているのはいちGo好きとしてはうれしい限りです。Goの良さは色々なところにあります、それは例えばシンプルな言語仕様だったり、標準ライブラリの充実度だったり、様々なサポートツール(
go get
、go vet
、goimports
、gorename
など)だったり。こういった側面はしばしばGoの良さとして語られますし私自身もそれには同意します。一方でそれらのわかりやすい利点の裏に隠れてしまっている影の立役者がいると思っています。そう、それこそがio.Reader
です。io.Reader
はその使い方を正しく理解するだけでGoのインターフェースという仕組みの強力さがわかる素晴らしいインターフェースのお手本であると思っています。1. io.Readerって?
一言で言ってしまえばバイト列を読み出すためのインターフェースです。Goを書いたことがある方はどこかで一度はコードの中で
io.Reader
に触れたことがあると思います。たとえその覚えがなくてもio.Reader
はGoを書いていれば様々な場所で出てきます。例えばファイル入力のとき、f, err := os.Open(filename)
の返り値f
はvar f *os.File
という型ですが、この*os.File
はio.Reader
インターフェースを持っています。他に頻出する例としてはhttp.Response
のBody
メンバがio.ReadCloser
というインターフェースを持っていますがこれはio.Reader
とio.Closer
の複合的なインターフェースです。io.Readerインターフェース
Goの標準ライブラリの中で
io.Reader
は以下のように定義されています(コメント略)。type Reader interface { Read(p []byte) (n int, err error) }初めてこの定義を見る方は「え、たったこれだけ?」と思われるかもしれませんが、これだけです。
Read([]byte) (int, error)
というシグネチャを持ったメソッドを実装しているありとあらゆる型がio.Reader
に該当します。これでio.Reader
がGoの様々な場面で出てくるという主張に納得していただけるのではないかと思います。同時にそういった方々にとってこのインターフェースを見ただけでは直感的に
Read
メソッドがどういった振る舞いをするのかは自明ではないでしょう。多くのライブラリはRead
メソッドを叩かなくて済むように様々な仕組みを提供しているため裏で何が行われているのかを知る機会があまりないのではないかと思います。例えばファイルを読むときは*bufio.Scanner
を使って一行ずつ読み込むでしょうし、HTTPのレスポンスボディはioutil.ReadAll
に渡して全部まるっと読んでしまえばよかったりするわけです。シンプルなユースケースであればごそっとバイトスライスに読み込んでしまえばよさそうですが、巨大なファイルを開く場合やストリームから継続的にデータを読み出すような使い方をする場合はio.Reader
を使わざるを得ないのでio.Reader
を介するAPI設計は重宝されます。Readメソッドの振る舞い
では具体的に
Read
メソッドが何をするか(することを期待されるか)を手続き的にご説明します。
- 与えられたバイトスライス
p []byte
を先頭から埋めていく。- 埋まったバイト数
n
と、埋める過程で発生したエラーerr
を返す。n < len(p)
の場合、err != nil
である可能性がある。- 組
(0, nil)
を返すことは非推奨。たとえばファイルから先頭
m
バイトを読みたい場合は以下のように記述します。f, err := os.Open(filename) p := make([]byte, m) // 1. スライス確保 n, err := f.Read(p) // 2. Read実行 if m < n { // 3. 返り値処理 log.Fatalf("%dバイト読もうとしましたが、%dバイトしか読めませんでした\n", n, m) } if err != nil { // 4. エラー処理 log.Fatalf("読込中にエラーが発生しました:%v\n", err) }
Read
メソッドは可能な限り渡されたスライスの全長を埋めることを期待されるため、あらかじめ読みたいバイト数分のサイズを持ったスライスを作成しておきます。- スライスの実態はポインタなのでこれをそのまま
Read
メソッドに渡すと中でこのスライスの中身を埋めてくれます。- Readでエラーが発生していても
n > 0
バイト埋められている可能性があるため先にその処理を行います。Goは多くの場合真っ先にエラー処理をすることが多いですが、Read
メソッドの返り値処理はその数少ない例外と言えるでしょう。ここでは要求したバイト数が読めなかったためlog.Fatalf
を呼んでいますが、場合によってはp[:n]
でなにかの処理をすることも想定できるでしょう(バッファリングなど)。- 最後にエラーを処理します。このとき
n < m
でもerr == nil
の可能性があることと、n == 0 && err == nil
であってもEOF
ではないことに注意しましょう(この組の返り値が非推奨になっている所以です)。2. io.Readerの良さ
io.Reader
というインターフェースとRead
メソッドの振る舞いをご理解いただいた上でなぜio.Reader
が素晴らしいのかを語っていきます。前述の通りio.Reader
はGoのあらゆる場面で出てくるインターフェースですが、その理由は圧倒的な汎用性にあります。このことを説明するのにもってこいなのが標準ライブラリに実装されているbufio.Reader
です。bufio.Readerとは
io.Reader
がインターフェースであるのに対してbufio.Reader
はio.Reader
を満たした構造体です。パッケージ名のbufio
が示唆する通りbufio.Reader
はio.Reader
オブジェクトにバッファリングの機能を実装します。公式ドキュメントも以下の通り:Reader implements buffering for an io.Reader object.
バッファリングの恩恵についてはこちらのGopherConの発表をご覧ください。ここでは発表の中で使われている単語数カウントのプログラムを借りつつ
io.Reader
の便利さの面にアプローチしていきます。まず単語数カウントのプログラムを作成していきます。package main import ( "fmt" "io" "log" "os" "unicode" ) var p [1]byte func readbyte(r io.Reader) (rune, error) { n, err := r.Read(p[:]) if n > 0 { return rune(p[0]), nil } return 0, err } func main() { filename := os.Args[1] f, err := os.Open(filename) if err != nil { log.Fatalf("cannot open file %q: %v", filename, err) } defer f.Close() words := 0 inword := false for { r, err := readbyte(f) if unicode.IsSpace(r) { if inword { words++ } inword = false } else { inword = true } if err == io.EOF { break } if err != nil { log.Fatalf("read failed: %v", err) } } fmt.Printf("%q: %d words\n", filename, words) }リンク先の発表のコードとは異なる部分がいくつかあります。
io.Reader
から1バイト読み込む際にエラーがあってもそのバイトをチェックする仕組みを追加- 単語の定義を
wc
コマンドと統一ベンチマークのためにMoby Dickのテキストを使用します。
$ go build slow_wc.go $ time ./slow_wc moby.txt "moby.txt": 215822 words ./slow_wc moby.txt 0.61s user 0.76s system 97% cpu 1.404 total $ time wc -w moby.txt 215822 moby.txt wc -w moby.txt 0.01s user 0.00s system 77% cpu 0.019 total
wc
の100倍くらい時間がかかっています。これは*os.File
のRead
メソッドが呼び出されるたびにシステムコールが発生しているのが原因なので、バッファリングによってパフォーマンスの改善が期待されます。先程のコードを次のように変更します。--- slow_wc.go 2019-11-24 03:40:14.000000000 +0900 +++ fast_wc.go 2019-11-24 03:40:17.000000000 +0900 @@ -1,6 +1,7 @@ package main import ( + "bufio" "fmt" "io" "log" @@ -26,11 +27,13 @@ } defer f.Close() + b := bufio.NewReader(f) + words := 0 inword := false for { - r, err := readbyte(f) + r, err := readbyte(b) if unicode.IsSpace(r) { if inword { words++$ go build ./fast_wc.go $ time ./fast_wc moby.txt "moby.txt": 215822 words ./fast_wc moby.txt 0.03s user 0.01s system 87% cpu 0.045 total
wc
の2倍程度の実行時間まで落ちました。コードの変化としてはファイルオブジェクトをbufio.Reader
で包んでreadbyte
関数に渡すオブジェクトを変更しただけですがここまでの差が出てきます。bufio.Reader
の中ではデフォルトで4096バイトごとに読み込みを行うので*os.File
のRead
メソッドの呼び出しがreadbyte
の呼び出し4096回ごとに1回になったと捉えられます。ここでポイントなのは
readbyte
関数に一切手を加えなくて良かったことです。よく知っている方はbufio.Reader
にはReadByte
というメソッドがあるのでわざわざreadbyte
を使う必要がないじゃないかと仰るかもしれませんがそれはその通りです(※というかむしろこの例ではunicode.IsSpace
を呼んでいるので本来はReadRune
を使うべきです)。しかしその議論は本質的ではなく、ここで主張したいことは、io.Reader
を受け取ることを前提に設計されたAPIは非常に頑強であるということです。上記の安全性も含めて記述するのであれば私であれば以下のようなコードを書くでしょう。
package main import ( "bufio" "fmt" "io" "log" "os" "unicode" ) func wordcount(r io.Reader) (int, error) { words, inword := 0, false b := bufio.NewReader(r) for { c, _, err := b.ReadRune() if unicode.IsSpace(c) { if inword { words++ } inword = false } else { inword = true } if err == io.EOF { return words, nil } if err != nil { return 0, err } } } func main() { filename := os.Args[1] f, err := os.Open(filename) if err != nil { log.Fatalf("cannot open file %q: %v", filename, err) } defer f.Close() words, err := wordcount(f) if err != nil { log.Fatalf("read failed: %v", err) } fmt.Printf("%q: %d words\n", filename, words) }このように単語数カウントを
wordcount
というio.Reader
を受け取る関数にしてしまうことで、ファイルだけでなくHTTPのレスポンスボディを含むありとあらゆるio.Reader
に適用できるようになります。このようにio.Reader
をうまく組み込んでいくことでio.Reader
を扱う様々なライブラリとシームレスに連携することができます。その1つの例が golang.org/x/text/transform です。io.Readerへの操作
シンプルな例として
io.Reader
から読み出す際に改行を落とすようなケースを考えます。golang.org/x/text/transform 内のtransform.Reader
を使うとio.Reader
にtransform.Transformer
を適用して様々な変換を行うことができます。文字単位で扱う場合には golang.org/x/text/runes 内に便利なtransform.Transformer
がいくつか定義されているためこちらを利用します。func IsNewline(r rune) bool { return r == '\n' } func RemoveNewline(r io.Reader) io.Reader { s := runes.Predicate(IsNewline) t := runes.Remove(s) return transform.NewReader(r, t) }たったこれだけであらゆる
io.Reader
型のオブジェクトにRemoveNewline
を適用するだけで改行をすべて落とすio.Reader
を生成できます。io.Readerは強力
ここまでお読みいただいたらもうお気づきかと思いますが、
bufio.Reader
やtransform.Reader
などはio.Reader
を加工しますがそれ自体がまたio.Reader
になっています。そのため、その他世の中に存在するありとあらゆるio.Reader
を要求する仕組みと連携することができるのです。このようにio.Reader
は非常にシンプルで役割が明快でありつつ強力なよくデザインされたインターフェースなのです。手前味噌にはなりますが、私が個人的に開発しているパーサコンビネータライブラリ pars では内部状態を表す
pars.State
はio.Reader
を受け取って必要なときに必要なだけそこから読み出してパーサを適用する設計になっています。Goで書かれたパーサコンビネータライブラリの先駆者として直接のデザインキューとなっている vektah/goparsify やそのインスピレーションとなっている prataprc/goparsec などがありますが、いずれも文字列やバイトスライスを受け取るAPI設計になっています。今私が開発しているバイオインフォマティクスの分野では非常に巨大なファイルを扱うことがよくあるため、こういったAPIは微妙に使い勝手が悪いのです。加えて、vektah/goparsify では空白を無視する仕組みがパーサの状態の中に実装されていますが、io.Reader
を受け取る設計にすることでその必要はなくなります。文字列リテラル内の空白以外をすべて落とすようなio.Reader
のマニピュレータを作ってpars.State
に渡してあげれば、特別な仕組みをパーサ状態内に作り込むことなく簡単に空白を除いてパーサを適用するといったこともできるのです。3. まとめ
io.Reader
はRead(p []byte) (n int, err error)
というメソッドを持った標準ライブラリに実装された特段何のひねりもない実にシンプルなインターフェースです。その反面、io.Reader
はGoという言語の哲学やインターフェースという機能の強力さをその身に宿した最強のインターフェースだと思っています。ここではカバーしませんでしたが対となるインターフェースであるio.Writer
と合わせて「Goを語る上で欠かせない、言語仕様の外にある最も重要な概念」だと言ってしまっても過言ではないというのが私の持論です。Goのインターフェースを使いこなすのは非常に難しいですが、コミュニティとしても広く受け入れられて使われている
io.Reader
を積極的に使ってみることでその利便性に気がつけるという側面もあるのではないかと思います。Go初心者から脱却する第一歩としてぜひio.Reader
を使い倒してみてください。
io.Reader
を知りio.Reader
を使いio.Reader
で作りましょう
- 投稿日:2019-11-25T01:00:59+09:00
Go の標準パッケージにないシステムコールを使う
はじめに
Qiita 初投稿です。
Go の golang.org/x/sys には、OS のシステムコールの Wrapper 関数が定義されていますが、全てが網羅されているわけではありません。
例えば、windows package を見てみると、WSARecv() などはありますが、WSASocket() がない。
https://godoc.org/golang.org/x/sys/windows#WSAEnumProtocols
本項では、これを自前で定義する方法について述べます。
検証環境
- Windows 10
- go version go1.13.3 windows/amd64
Syscall の構造
まず、
WSARecv()
がどのように定義されているか見ていきます。
WSARecv() の定義はgolang.org/x/sys/windows/zsyscall_windows.go
にあります。func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) { r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0) if r1 == socket_error { if e1 != 0 { err = errnoErr(e1) } else { err = syscall.EINVAL } } return }はい、謎の関数
syscall.Syscall9()
が現れました。
意味不明すぎて考えることをやめたくなりますが、恐れることはありません。ファイル先頭に次のようにある通り、これは自動生成コードです。// Code generated by 'go generate'; DO NOT EDIT.
関数定義
同ディレクトリの
syscall_windows.go
に生成する関数の定義があります。//sys WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv
長いので細かく分けて見ていきます。
//sys
system call のコード生成であることを表す。このコメントを、後述の
mkwinsyscall
が拾う。WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error)
定義する関数シグネチャ。WSABuf とか Overlapped とかの型は、windows package で定義されている。
[failretval==socket_error]
system call が戻り値 socket_error を返したときにエラーとする。socket_error は windows package の package private な定数。
const socket_error = uintptr(^uint32(0))MSDN より
If no error occurs and the receive operation has completed immediately, WSARecv returns zero. In this case, the completion routine will have already been scheduled to be called once the calling thread is in the alertable state. Otherwise, a value of SOCKET_ERROR is returned
= ws2_32.WSARecv
上述の関数を、ws2_32.dll 内の WSARecv 関数にマッピングする。
コード生成
生成コマンド定義は
mksyscall.go
にあります。//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go
//go:generate go run
までは、Go generate の書式。
golang.org/x/sys/windows/mkwinsyscall
がzsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go
から//sys
の定義を拾い上げて、zsyscall_windows.go
を生成しているようです。自前で WSASocket() を定義してみる
仕組みはわかったので、自前で WSASocket() を定義してみます。
import "golang.org/x/sys/windows" const ( wsaprotocol_len = 255 max_protocol_chain = 7 invalid_socket = ^windows.Handle(0) ) type GROUP uint32 type GUID struct { Data1 uint32 Data2 uint16 Data3 uint16 Data4 [8]byte } type WSAPROTOCOLCHAIN struct { ChainLen int32 ChainEntries [max_protocol_chain]uint32 } type WSAPROTOCOL_INFO struct { ServiceFlags1 uint32 ServiceFlags2 uint32 ServiceFlags3 uint32 ServiceFlags4 uint32 ProviderFlags uint32 ProviderID GUID CatalogEntryID uint32 ProtocolChain WSAPROTOCOLCHAIN Version int32 AddressFamily int32 MaxSockAddr int32 MinSockAddr int32 SocketType int32 Protocol int32 ProtocolMaxOffset int32 NetworkByteOrder int32 SecurityScheme int32 MessageSize uint32 ProviderReserved uint32 Protocols [wsaprotocol_len + 1]uint16 } //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinsys.go winsys.go //sys WSASocket(af int32, tp int32, protocol int32, protocolInfo *WSAPROTOCOL_INFO, g GROUP, flags uint32) (socket windows.Handle, err error) [failretval==invalid_socket] = ws2_32.WSASocketWなにやら構造体やら色々でてきましたが、これらは C の構造体やマクロ定義を移植したものです。(この辺の情報を一々調べて定義するのが面倒。。。)
ここで出てきた WORD, DWORD, int の対応は次の通り。
C Go WORD uint16 DWORD uint32 int int32 コード生成
//go:generate
を記述した package のディレクトリで次のコマンドを実行すると、zmysys.go
が生成されます。$ go generate
実際に使ってみる
正常ケースとエラーケースを試してみます。
まずは正常ケース。sock, err := WSASocket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP, nil, 0, 0) fmt.Println(sock, err) // => 344 <nil>正しくSOCKETが生成され、エラーも発生していないようです。
次にエラーケース。
const badArg int32 = 9999 sock, err := WSASocket(badArg, badArg, badArg, nil, 0, 0) fmt.Println(sock, err) // => 18446744073709551615 The support for the specified socket type does not exist in this address family.不正な値を与えたので、WSAESOCKTNOSUPPORT のエラーが返ってきました。
このメッセージは FormatMessageWで取得されたものです。エラーコードは
syscall.Syscall<N>()
の3番目の戻り値として返ってきます。その実装は runtime のアセンブラで定義されていてコードを追いきれませんでしたが、GetLastError()
の結果を返してきている模様。(たぶんこの辺)
本来、WSAXXX()
のエラーコードはWSAGetLastError()
で取得するものですが、WSAGetLastError()
はGetLastError()
の Wrapper であるがため、問題なく動いているようです。https://stackoverflow.com/questions/15586224/is-wsagetlasterror-just-an-alias-for-getlasterror
まとめ
標準package で定義されていない system call も、
golang.org/x/sys/windows/mkwinsyscall
を使えば、比較的簡単に呼び出し可能。