学习笔记 | Backend master class [Golang, PostgreSQL, Docker]

后端开发大师课 | Backend master class [Golang, PostgreSQL, Docker]

目录

Lecture 1 | 设计数据库schema并用dbdiagram.io生成sql

通过 dbdiagram.io 设计数据库的 schema 并生成 postgresql 的 sql 代码。

分享链接:https://dbdiagram.io/d/62ba96ad69be0b672c5496b8

// 账户表
table accounts as A {
  id bigserial [pk]
  owner varchar [not null]
  balance bigint [not null]
  currency varchar [not null]
  created_at timestamptz [not null, default: `now()`]
  
  indexes {
    owner
  }
}

// 交易记录表
table entries {
  id bigserial [pk]
  account_id bigint [ref: > A.id, not null]
  amount bigint [not null, note: 'can be negative or postive']
  created_at timestamptz [not null, default: `now()`]
  
  indexes {
    account_id
  }
}

// 转账记录表
table transfers {
  id bigserial [pk]
  from_account_id bigint [ref: > A.id, not null]
  to_account_id bigint [ref: > A.id, not null]
  amount bigint [not null, note: 'must be postive']
  created_at timestamptz [not null, default: `now()`]
  
  indexes {
    from_account_id
    to_account_id
    (from_account_id, to_account_id)
  }
}

alt

CREATE TABLE "accounts" (
  "id" bigserial PRIMARY KEY,
  "owner" varchar NOT NULL,
  "balance" bigint NOT NULL,
  "currency" varchar NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

CREATE TABLE "entries" (
  "id" bigserial PRIMARY KEY,
  "account_id" bigint NOT NULL,
  "amount" bigint NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

CREATE TABLE "transfers" (
  "id" bigserial PRIMARY KEY,
  "from_account_id" bigint NOT NULL,
  "to_account_id" bigint NOT NULL,
  "amount" bigint NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now())
);

CREATE INDEX ON "accounts" ("owner");

CREATE INDEX ON "entries" ("account_id");

CREATE INDEX ON "transfers" ("from_account_id");

CREATE INDEX ON "transfers" ("to_account_id");

CREATE INDEX ON "transfers" ("from_account_id", "to_account_id");

COMMENT ON COLUMN "entries"."amount" IS 'can be negative or postive';

COMMENT ON COLUMN "transfers"."amount" IS 'must be postive';

ALTER TABLE "entries" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id");

ALTER TABLE "transfers" ADD FOREIGN KEY ("from_account_id") REFERENCES "accounts" ("id");

ALTER TABLE "transfers" ADD FOREIGN KEY ("to_account_id") REFERENCES "accounts" ("id");

Lecture 2 | 安装并使用docker+postgres+tableplus创建数据库schema

  • docker:用来安装软件,相当于一个小型虚拟机。
  • postgres:数据库管理系统。
  • tableplus:与DBMS交互的界面化软件。

docker 相关命令:

  • docker ps 查看当前运行中的容器

  • docker images 查看镜像列表

  • docker rm container-id 删除指定 id 的容器

  • docker stop/start container-id 停止/启动指定 id 的容器

  • docker rmi image-id 删除指定 id 的镜像

  • docker volume ls 查看 volume 列表

  • docker network ls 查看网络列表

  • docker pull 拉取镜像到本地

  • docker run 使用本地镜像启动容器

  • docker exec 在容器中执行命令

  • docker logs

拉取 postgres 镜像:

  • docker pull postgres:12-alpine

使用镜像启动 postgre 容器:

  • docker run --name postgres12 -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=123456 -d postgres:12-alpine

在容器中执行 psql 命令:

  • docker exec -it postgres12 psql -U root

tableplus:

alt

alt

alt

容器内:

➜  ~ docker exec -it postgres12 psql -U root
psql (12.11)
Type "help" for help.

root=# select now();
             now              
------------------------------
 2022-06-28 07:03:29.51169+00
(1 row)

root=# \l
                             List of databases
   Name    | Owner | Encoding |  Collate   |   Ctype    | Access privileges 
-----------+-------+----------+------------+------------+-------------------
 postgres  | root  | UTF8     | en_US.utf8 | en_US.utf8 | 
 root      | root  | UTF8     | en_US.utf8 | en_US.utf8 | 
 template0 | root  | UTF8     | en_US.utf8 | en_US.utf8 | =c/root          +
           |       |          |            |            | root=CTc/root
 template1 | root  | UTF8     | en_US.utf8 | en_US.utf8 | =c/root          +
           |       |          |            |            | root=CTc/root
(4 rows)

root=# lc root

root-# \d
              List of relations
 Schema |       Name       |   Type   | Owner 
--------+------------------+----------+-------
 public | accounts         | table    | root
 public | accounts_id_seq  | sequence | root
 public | entries          | table    | root
 public | entries_id_seq   | sequence | root
 public | transfers        | table    | root
 public | transfers_id_seq | sequence | root
(6 rows)

root-# \d accounts
                                       Table "public.accounts"
   Column   |           Type           | Collation | Nullable |               Default                
------------+--------------------------+-----------+----------+--------------------------------------
 id         | bigint                   |           | not null | nextval('accounts_id_seq'::regclass)
 owner      | character varying        |           | not null | 
 balance    | bigint                   |           | not null | 
 currency   | character varying        |           | not null | 
 created_at | timestamp with time zone |           | not null | now()
Indexes:
    "accounts_pkey" PRIMARY KEY, btree (id)
    "accounts_owner_idx" btree (owner)
Referenced by:
    TABLE "entries" CONSTRAINT "entries_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id)
    TABLE "transfers" CONSTRAINT "transfers_from_account_id_fkey" FOREIGN KEY (from_account_id) REFERENCES accounts(id)
    TABLE "transfers" CONSTRAINT "transfers_to_account_id_fkey" FOREIGN KEY (to_account_id) REFERENCES accounts(id)

root-# \d entries
                                       Table "public.entries"
   Column   |           Type           | Collation | Nullable |               Default               
------------+--------------------------+-----------+----------+-------------------------------------
 id         | bigint                   |           | not null | nextval('entries_id_seq'::regclass)
 account_id | bigint                   |           |          | 
 amount     | bigint                   |           | not null | 
 created_at | timestamp with time zone |           | not null | now()
Indexes:
    "entries_pkey" PRIMARY KEY, btree (id)
    "entries_account_id_idx" btree (account_id)
Foreign-key constraints:
    "entries_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id)

root-# \d transfers
                                         Table "public.transfers"
     Column      |           Type           | Collation | Nullable |                Default                
-----------------+--------------------------+-----------+----------+---------------------------------------
 id              | bigint                   |           | not null | nextval('transfers_id_seq'::regclass)
 from_account_id | bigint                   |           |          | 
 to_account_id   | bigint                   |           |          | 
 amount          | bigint                   |           | not null | 
 created_at      | timestamp with time zone |           | not null | now()
Indexes:
    "transfers_pkey" PRIMARY KEY, btree (id)
    "transfers_from_account_id_idx" btree (from_account_id)
    "transfers_from_account_id_to_account_id_idx" btree (from_account_id, to_account_id)
    "transfers_to_account_id_idx" btree (to_account_id)
Foreign-key constraints:
    "transfers_from_account_id_fkey" FOREIGN KEY (from_account_id) REFERENCES accounts(id)
    "transfers_to_account_id_fkey" FOREIGN KEY (to_account_id) REFERENCES accounts(id)

Lecture 3 | 在golang中编写和运行数据库迁移脚本

golang数据库迁移包:https://github.com/golang-migrate/migrate

安装:

  • brew install golang-migrate

命令(主要关注create、up、down这三个命令):

➜  ~ migrate -help
Usage: migrate OPTIONS COMMAND [arg...]
       migrate [ -version | -help ]

Options:
  -source          Location of the migrations (driver://url)
  -path            Shorthand for -source=file://path
  -database        Run migrations against this database (driver://url)
  -prefetch N      Number of migrations to load in advance before executing (default 10)
  -lock-timeout N  Allow N seconds to acquire database lock (default 15)
  -verbose         Print verbose logging
  -version         Print version
  -help            Print usage

Commands:
  create [-ext E] [-dir D] [-seq] [-digits N] [-format] [-tz] NAME
	   Create a set of timestamped up/down migrations titled NAME, in directory D with extension E.
	   Use -seq option to generate sequential up/down migrations with N digits.
	   Use -format option to specify a Go time format string. Note: migrations with the same time cause "duplicate migration version" error.
           Use -tz option to specify the timezone that will be used when generating non-sequential migrations (defaults: UTC).

  goto V       Migrate to version V
  up [N]       Apply all or N up migrations
  down [N] [-all]    Apply all or N down migrations
	Use -all to apply all down migrations
  drop [-f]    Drop everything inside database
	Use -f to bypass confirmation
  force V      Set version V but don't run migration (ignores dirty state)
  version      Print current migration version

Source drivers: s3, gitlab, go-bindata, file, bitbucket, github, github-ee, godoc-vfs, gcs
Database drivers: crdb-postgres, clickhouse, cockroach, cockroachdb, firebird, firebirdsql, mysql, postgres, postgresql, redshift, sqlserver, cassandra, mongodb, mongodb+srv, spanner, stub, neo4j, pgx

创建迁移文件(sql):

➜  simplebank migrate create -ext sql -dir db/migration -seq init_schema  
/Users/tangyiheng/Desktop/Backend master class [Golang, Postgres, Docker]/simplebank/db/migration/000001_init_schema.up.sql
/Users/tangyiheng/Desktop/Backend master class [Golang, Postgres, Docker]/simplebank/db/migration/000001_init_schema.down.sql
  • 升级:migrate up,将执行 up 脚本文件
  • 降级:migrate down,将执行 down 脚本文件

在 postgres 容器中创建数据库:

docker exec -it postgres12 createdb --username=root --owner=root simple_bank

进入容器中的 postgres 控制台:

docker exec -it postgres12 psql -U root simple_bank

使用 migrate 对数据库版本进行升级:

migrate -path db/migration -database "postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable" -verbose up

同理使用 migrate 对数据库版本进行回退:

migrate -path db/migration -database "postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable" -verbose down

编写 Makefile 方便配置环境:

postgres:
	docker run --name postgres12 -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=123456 -d postgres:12-alpine

createdb:
	docker exec -it postgres12 createdb --username=root --owner=root simple_bank

dropdb:
	docker exec -it postgres12 dropdb simple_bank

migrateup:
	migrate -path db/migration -database "postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable" -verbose up

migratedown:
	migrate -path db/migration -database "postgresql://root:123456@localhost:5432/simple_bank?sslmode=disable" -verbose down

.PHONY: postgres createdb dropdb migrateup migratedown

Lecture 4 | 从sql生成CRUD的golang代码

CRUD:

  • create
  • read
  • update
  • delete

考虑以下几种sql生成方案:

alt

更正:目前已经支持 MySQL。

安装 sqlc:

brew install sqlc

sqlc 相关命令:

➜  simplebank sqlc help 
Usage:
  sqlc [command]

Available Commands:
  compile     Statically check SQL for syntax and type errors
  completion  Generate the autocompletion script for the specified shell
  generate    Generate Go code from SQL
  help        Help about any command
  init        Create an empty sqlc.yaml settings file
  version     Print the sqlc version number

Flags:
  -x, --experimental   enable experimental features (default: false)
  -f, --file string    specify an alternate config file (default: sqlc.yaml)
  -h, --help           help for sqlc

Use "sqlc [command] --help" for more information about a command.

主要关注以下命令:

  • compile:检查 sql 语法错误。
  • generate:从 sql 生成 golang 的 CRUD 代码。
  • init:生成 sqlc 的 yaml 配置文件。

编写 CRUD 的 query:

-- name: CreateAccount :one
INSERT INTO accounts (
    owner,
    balance,
    currency
) VALUES (
    $1, $2, $3
)
RETURNING *;

-- name: GetAccount :one
SELECT * FROM accounts
WHERE id = $1 LIMIT 1;

-- name: ListAccounts :many
SELECT * FROM accounts
ORDER BY id
LIMIT $1
OFFSET $2;

-- name: UpdateAccount :one
UPDATE accounts
SET balance = $2
WHERE id = $1
RETURNING *;

-- name: DeleteAccount :exec
DELETE FROM accounts
WHERE id = $1;

生成以下三个 golang 文件:

  • XXX.sql.go:对应之前写的CRUD代码,它将入参单独封装成为了一个结构体。
// Code generated by sqlc. DO NOT EDIT.
// source: account.sql

package db

import (
	"context"
)

const createAccount = `-- name: CreateAccount :one
INSERT INTO accounts (
    owner,
    balance,
    currency
) VALUES (
    $1, $2, $3
)
RETURNING id, owner, balance, currency, created_at
`

type CreateAccountParams struct {
	Owner    string `json:"owner"`
	Balance  int64  `json:"balance"`
	Currency string `json:"currency"`
}

func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (Account, error) {
	row := q.db.QueryRowContext(ctx, createAccount, arg.Owner, arg.Balance, arg.Currency)
	var i Account
	err := row.Scan(
		&i.ID,
		&i.Owner,
		&i.Balance,
		&i.Currency,
		&i.CreatedAt,
	)
	return i, err
}

const deleteAccount = `-- name: DeleteAccount :exec
DELETE FROM accounts
WHERE id = $1
`

func (q *Queries) DeleteAccount(ctx context.Context, id int64) error {
	_, err := q.db.ExecContext(ctx, deleteAccount, id)
	return err
}

const getAccount = `-- name: GetAccount :one
SELECT id, owner, balance, currency, created_at FROM accounts
WHERE id = $1 LIMIT 1
`

func (q *Queries) GetAccount(ctx context.Context, id int64) (Account, error) {
	row := q.db.QueryRowContext(ctx, getAccount, id)
	var i Account
	err := row.Scan(
		&i.ID,
		&i.Owner,
		&i.Balance,
		&i.Currency,
		&i.CreatedAt,
	)
	return i, err
}

const listAccounts = `-- name: ListAccounts :many
SELECT id, owner, balance, currency, created_at FROM accounts
ORDER BY id
LIMIT $1
OFFSET $2
`

type ListAccountsParams struct {
	Limit  int32 `json:"limit"`
	Offset int32 `json:"offset"`
}

func (q *Queries) ListAccounts(ctx context.Context, arg ListAccountsParams) ([]Account, error) {
	rows, err := q.db.QueryContext(ctx, listAccounts, arg.Limit, arg.Offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []Account
	for rows.Next() {
		var i Account
		if err := rows.Scan(
			&i.ID,
			&i.Owner,
			&i.Balance,
			&i.Currency,
			&i.CreatedAt,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Close(); err != nil {
		return nil, err
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const updateAccount = `-- name: UpdateAccount :one
UPDATE accounts
SET balance = $2
WHERE id = $1
RETURNING id, owner, balance, currency, created_at
`

type UpdateAccountParams struct {
	ID      int64 `json:"id"`
	Balance int64 `json:"balance"`
}

func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error) {
	row := q.db.QueryRowContext(ctx, updateAccount, arg.ID, arg.Balance)
	var i Account
	err := row.Scan(
		&i.ID,
		&i.Owner,
		&i.Balance,
		&i.Currency,
		&i.CreatedAt,
	)
	return i, err
}

  • db.go:便于我们使用数据库和事务。
package db

import (
	"context"
	"database/sql"
)

type DBTX interface {
	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
	PrepareContext(context.Context, string) (*sql.Stmt, error)
	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}

func New(db DBTX) *Queries {
	return &Queries{db: db}
}

type Queries struct {
	db DBTX
}

func (q *Queries) WithTx(tx *sql.Tx) *Queries {
	return &Queries{
		db: tx,
	}
}
  • models.go:定义了与数据库单表对应的结构体
package db

import (
	"time"
)

type Account struct {
	ID        int64     `json:"id"`
	Owner     string    `json:"owner"`
	Balance   int64     `json:"balance"`
	Currency  string    `json:"currency"`
	CreatedAt time.Time `json:"created_at"`
}

type Entry struct {
	ID        int64 `json:"id"`
	AccountID int64 `json:"account_id"`
	// can be negative or positive
	Amount    int64     `json:"amount"`
	CreatedAt time.Time `json:"created_at"`
}

type Transfer struct {
	ID            int64 `json:"id"`
	FromAccountID int64 `json:"from_account_id"`
	ToAccountID   int64 `json:"to_account_id"`
	// must be positive
	Amount    int64     `json:"amount"`
	CreatedAt time.Time `json:"created_at"`
}

Lecture 4 | 为CURD的golang代码编写单元测试

为 go 自带的 database/sql 安装 postgres 驱动 pq(https://github.com/lib/pq)。

  • go get github.com/lib/pq
全部评论
视频:https://www.youtube.com/playlist?list=PLy_6D98if3ULEtXtNSY_2qN21VCKgoQAE
点赞 回复 分享
发布于 2022-06-21 20:29
源码:https://github.com/techschool/simplebank
点赞 回复 分享
发布于 2022-06-21 20:30

相关推荐

2024-11-09 01:22
已编辑
东南大学 Java
高级特工穿山甲:羡慕,我秋招有家企业在茶馆组织线下面试,约我过去“喝茶详谈”😢结果我去了发现原来是人家喝茶我看着
点赞 评论 收藏
分享
评论
点赞
2
分享
牛客网
牛客企业服务