Golang: RESTful API, использующий временную таблицу с MariaDB

Golang: RESTful API, использующий временную таблицу с MariaDB

8 апреля 2023 г.

Временные таблицы – это тип таблицы базы данных, в которой хранятся исторические данные, что позволяет пользователям запрашивать данные в том виде, в каком они существовали на момент конкретные моменты времени. Эти таблицы отслеживают изменения данных с течением времени и сохраняют историю всех изменений. Это может быть полезно для аудита, соответствия и отслеживания изменений с течением времени. Временные таблицы отличаются от обычных тем, что в них хранится как текущее состояние данных, так и история изменений.< /p>

MariaDB — это популярная система управления реляционными базами данных с открытым исходным кодом, поддерживающая темпоральные таблицы. Пользователи могут создавать временные таблицы и управлять ими с помощью SQL. Это позволяет пользователям легко отслеживать изменения данных и получать доступ к историческим версиям записей.

Соответственно, на его официальном веб-сайте темпоральные таблицы поддерживаются в трех формах: системная версия , время приложения и битемпоральный. В этом примере мы будем использовать версию системы.

В этой статье мы создадим докеризованный REST API в Go, который реализует такой вариант использования.

Модель домена

Мы создадим простой API, который предлагает конечные точки для:

* список сотрудников; * список сотрудников по id; * создать сотрудника; * обновить сотрудника; * получить историческую информацию о сотруднике; * удалить сотрудника.

Наши столы:

No alt text provided for this image

schema_migrations — это таблица, используемая для миграция базы данных.

API

Я не буду вдаваться в подробности того, как я спроектировал и реализовал сам API, так как он заслуживает особого внимания. эксклюзивный пост для него. Вкратце, вот ключевые моменты:

* Я использую httptreemux в качестве маршрутизатора; * Я написал несколько пользовательских промежуточных программ как для обработки ошибок, так и для общего ведения журнала, что сокращает объем кода в функциях обработчика. существенно; * Я решил использовать стратегию управления версиями пути для API.

Ресурс сотрудников

Вот наши обработчики (handlers/v1/employees/employees.go):

// Copyright (c) 2023 Tiago Melo. All rights reserved.
// Use of this source code is governed by the MIT License that can be found in
// the LICENSE file.
package employees


import (
    "context"
    "database/sql"
    "encoding/json"
    "errors"
    "fmt"
    "net/http"
    "strconv"


    mariaDb "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db"
    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db/employees"
    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db/employees/models"
    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/validate"
    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/web"
    v1Web "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/web/v1"
)


type Handlers struct {
    Db *sql.DB
}


// handleGetEmployeeByIdError handles errors when getting an
// employee by its id.
func handleGetEmployeeByIdError(err error, id uint) error {
    if errors.Is(err, mariaDb.ErrDBNotFound) {
        return v1Web.NewRequestError(err, http.StatusNotFound)
    }
    return fmt.Errorf("ID[%d]: %w", id, err)
}


// handleCreateEmployeeError handles errors when creating an
// employee.
func handleCreateEmployeeError(err error) error {
    if errors.Is(err, mariaDb.ErrDBDuplicatedEntry) {
        return v1Web.NewRequestError(err, http.StatusConflict)
    }
    return fmt.Errorf("unable to create employee: %w", err)
}


// handleUpdateEmployeeByIdErr handles errors when updating an
// employee by its id.
func handleUpdateEmployeeByIdErr(err error, id uint) error {
    if errors.Is(err, mariaDb.ErrDBNotFound) {
        return v1Web.NewRequestError(err, http.StatusNotFound)
    }
    return fmt.Errorf("ID[%d]: %w", id, err)
}


// GetById returns a current employee with given id.
func (h Handlers) GetById(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    idParam := web.Param(r, "id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid id: %v", idParam), http.StatusBadRequest)
    }
    employee, err := employees.GetById(ctx, h.Db, uint(id))
    if err != nil {
        return handleGetEmployeeByIdError(err, uint(id))
    }
    return web.Respond(ctx, w, employee, http.StatusOK)
}


// GetAll returns all current employees.
func (h *Handlers) GetAll(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    employees, err := employees.GetAll(ctx, h.Db)
    if err != nil {
        return fmt.Errorf("unable to query employees: %w", err)
    }
    return web.Respond(ctx, w, employees, http.StatusOK)
}


// Create creates an employee.
func (h Handlers) Create(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    defer r.Body.Close()
    var newEmployee models.NewEmployee
    if err := json.NewDecoder(r.Body).Decode(&newEmployee); err != nil {
        return v1Web.NewRequestError(err, http.StatusBadRequest)
    }
    if err := validate.Check(newEmployee); err != nil {
        return fmt.Errorf("validating data: %w", err)
    }
    employee, err := employees.Create(ctx, h.Db, &newEmployee)
    if err != nil {
        return handleCreateEmployeeError(err)
    }
    return web.Respond(ctx, w, employee, http.StatusCreated)
}


// Update updates a current employee.
func (h Handlers) Update(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    idParam := web.Param(r, "id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid id: %v", idParam), http.StatusBadRequest)
    }
    var updateEmployee models.UpdateEmployee
    if err := json.NewDecoder(r.Body).Decode(&updateEmployee); err != nil {
        return v1Web.NewRequestError(err, http.StatusBadRequest)
    }
    updatedEmployee, err := employees.Update(ctx, h.Db, uint(id), &updateEmployee)
    if err != nil {
        return handleUpdateEmployeeByIdErr(err, uint(id))
    }
    return web.Respond(ctx, w, updatedEmployee, http.StatusOK)
}


// Delete deletes a current employee.
func (h Handlers) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    idParam := web.Param(r, "id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid id: %v", idParam), http.StatusBadRequest)
    }
    if err = employees.Delete(ctx, h.Db, uint(id)); err != nil {
        return fmt.Errorf("ID[%d]: %w", id, err)
    }
    return web.Respond(ctx, w, nil, http.StatusNoContent)
}

А вот и наши коррелированные функции БД (db/employees/employees.go):

// Copyright (c) 2023 Tiago Melo. All rights reserved.
// Use of this source code is governed by the MIT License that can be found in
// the LICENSE file.
package employees


import (
    "context"
    "database/sql"


    "github.com/go-sql-driver/mysql"
    mariaDb "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db"
    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db/employees/models"
)


// For ease of unit testing.
var (
    readEmployee = func(row *sql.Row, dest ...any) error {
        return row.Scan(dest...)
    }
    readEmployees = func(rows *sql.Rows, dest ...any) error {
        return rows.Scan(dest...)
    }
)


// GetById returns a current employee with given id.
func GetById(ctx context.Context, db *sql.DB, id uint) (*models.Employee, error) {
    q := `
    SELECT id, first_name, last_name, salary, department
    FROM employees
    WHERE id = ?
    `
    var employee models.Employee
    row := db.QueryRowContext(ctx, q, id)
    if err := readEmployee(row,
        &employee.Id,
        &employee.FirstName,
        &employee.LastName,
        &employee.Salary,
        &employee.Department,
    ); err != nil {
        return nil, mariaDb.ErrDBNotFound
    }
    return &employee, nil
}


// GetAll returns all current employees.
func GetAll(ctx context.Context, db *sql.DB) ([]models.Employee, error) {
    q := `
    SELECT id, first_name, last_name, salary, department
    FROM employees
    `
    employees := make([]models.Employee, 0)
    rows, err := db.QueryContext(ctx, q)
    if err != nil {
        return employees, err
    }
    defer rows.Close()
    for rows.Next() {
        var employee models.Employee
        if err = readEmployees(rows,
            &employee.Id,
            &employee.FirstName,
            &employee.LastName,
            &employee.Salary,
            &employee.Department,
        ); err != nil {
            return employees, err
        }
        employees = append(employees, employee)
    }
    return employees, nil
}


// Create creates an employee.
func Create(ctx context.Context, db *sql.DB, newEmployee *models.NewEmployee) (*models.Employee, error) {
    q := `
    INSERT INTO
        employees(first_name, last_name, salary, department)
    VALUES
        (?, ?, ?, ?)
    RETURNING
        id, first_name, last_name, salary, department
    `
    var employee models.Employee
    row := db.QueryRowContext(ctx, q, newEmployee.FirstName, newEmployee.LastName, newEmployee.Salary, newEmployee.Department)
    if err := readEmployee(row,
        &employee.Id,
        &employee.FirstName,
        &employee.LastName,
        &employee.Salary,
        &employee.Department,
    ); err != nil {
        if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == mariaDb.UniqueViolation {
            return nil, mariaDb.ErrDBDuplicatedEntry
        }
        return nil, err
    }
    return &employee, nil
}


// handleEmployeeChanges updates the changed properties.
func handleEmployeeChanges(updateEmployee *models.UpdateEmployee, dbEmployee *models.Employee) {
    if updateEmployee.FirstNameIsFulfilled() {
        dbEmployee.FirstName = *updateEmployee.FirstName
    }
    if updateEmployee.LastNameIsFulfilled() {
        dbEmployee.LastName = *updateEmployee.LastName
    }
    if updateEmployee.SalaryIsFulfilled() {
        dbEmployee.Salary = *updateEmployee.Salary
    }
    if updateEmployee.DepartmentIsFulfilled() {
        dbEmployee.Department = *updateEmployee.Department
    }
}


// Update updates an employee.
func Update(ctx context.Context, db *sql.DB, employeeId uint, updateEmployee *models.UpdateEmployee) (*models.Employee, error) {
    q := `
    UPDATE employees
    SET 
        first_name = ?,
        last_name = ?,
        salary = ?,
        department = ?
    WHERE
        id = ?
    `
    dbEmployee, err := GetById(ctx, db, employeeId)
    if err != nil {
        return nil, err
    }
    handleEmployeeChanges(updateEmployee, dbEmployee)
    _, err = db.ExecContext(ctx, q, dbEmployee.FirstName, dbEmployee.LastName, dbEmployee.Salary, dbEmployee.Department, dbEmployee.Id)
    return dbEmployee, err
}


// Delete deletes an employee.
func Delete(ctx context.Context, db *sql.DB, id uint) error {
    q := `
    DELETE FROM
        employees
    WHERE id = ?
    `
    _, err := db.ExecContext(ctx, q, id)
    return err
}

Как видим, старый добрый CRUD, ничего особенного здесь нет.

Давайте создадим нашего первого сотрудника:

curl 'http://localhost:3000/v1/employee' 
> --header 'Content-Type: application/json' 
> --data '{
>     "first_name": "John",
>     "last_name": "Doe",
>     "salary": 1234.56,
>     "department": "IT"
> }'


{"id":1,"first_name":"John","last_name":"Doe","department":"IT","salary":1234.56}

Теперь предположим, что мы хотим обновить зарплату этого сотрудника через несколько лет. Для этого нам нужно вручную изменить метку времени по умолчанию в MariaDB, чтобы она возвращает отметку времени в будущем.

Для этого я создал две конечные точки:

* GET v1/db/timestamp/advance: переводит отметку времени по умолчанию в MariaDB на случайное число лет от 1 до 5; * GET v1/db/timestamp/default: устанавливает отметку времени по умолчанию на текущую дату.

Давайте вызовем конечную точку для опережения метки времени:

curl 'http://localhost:3000/v1/db/timestamp/advance'

{"timestamp":"2025-04-06"}

Мы видим, что мы случайным образом передвинули текущий год (2023) на два года (2025), а это значит, что MariaDB думает, что мы находимся в 2025 году.

Давайте тогда обновим зарплату сотрудника:

curl --request PUT 'http://localhost:3000/v1/employee/1' 
--header 'Content-Type: application/json' 
--data '{
    "salary": 2500
}'

{"id":1,"first_name":"John","last_name":"Doe","department":"IT","salary":2500}

Теперь мы увидим мощь темпоральных таблиц. Что, если мы хотим запросить все исторические изменения для этого сотрудника?

Ресурс истории сотрудников

У нас есть три доступных конечных точки:

* v1/employee/{id}/history/all, что позволяет нам проверять все исторические данные; * v1/employee/{id}/history/{timestamp}, которая позволяет нам получать исторические данные в заданный момент времени; * v1/employee/{id}/history/{start_timestamp}/{end_timestamp}, что позволяет получить исторические данные между двумя датами.

Вот наши обработчики (handlers/v1/employees/history/history.go):

// Copyright (c) 2023 Tiago Melo. All rights reserved.
// Use of this source code is governed by the MIT License that can be found in
// the LICENSE file.
package history


import (
    "context"
    "database/sql"
    "fmt"
    "net/http"
    "strconv"
    "time"


    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db/employees"
    "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/web"
    v1Web "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/web/v1"
)


type Handlers struct {
    Db *sql.DB
}


// GetAll returns all historical data for a given employee.
func (h Handlers) GetAll(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    idParam := web.Param(r, "id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid id: %v", idParam), http.StatusBadRequest)
    }
    employeeHistory, err := employees.GetAllHistory(ctx, h.Db, uint(id))
    if err != nil {
        return fmt.Errorf("ID[%d]: %w", id, err)
    }
    return web.Respond(ctx, w, employeeHistory, http.StatusOK)
}


// AtPointInTime returns historical data for a given employee at a point in time.
func (h Handlers) AtPointInTime(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    idParam := web.Param(r, "id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid id: %v", idParam), http.StatusBadRequest)
    }
    timestampParam := web.Param(r, "timestamp")
    timestamp, err := strconv.ParseInt(timestampParam, 10, 64)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid timestamp: %v", timestampParam), http.StatusBadRequest)
    }
    employeeHistory, err := employees.AtPointInTime(ctx, h.Db, uint(id), time.Unix(timestamp, 0).Format("2006-01-02 15:04:05"))
    if err != nil {
        return fmt.Errorf("ID[%d]: %w", timestamp, err)
    }
    return web.Respond(ctx, w, employeeHistory, http.StatusOK)
}


// BetweenDates returns historical data for a given employee between dates.
func (h Handlers) BetweenDates(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
    idParam := web.Param(r, "id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid id: %v", idParam), http.StatusBadRequest)
    }
    startTimestampParam := web.Param(r, "startTimestamp")
    startTimestamp, err := strconv.ParseInt(startTimestampParam, 10, 64)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid start timestamp: %v", startTimestampParam), http.StatusBadRequest)
    }
    endTimestampParam := web.Param(r, "endTimestamp")
    endTimestamp, err := strconv.ParseInt(endTimestampParam, 10, 64)
    if err != nil {
        return v1Web.NewRequestError(fmt.Errorf("invalid end timestamp: %v", endTimestampParam), http.StatusBadRequest)
    }
    employeeHistory, err := employees.BetweenDates(ctx, h.Db, uint(id), time.Unix(startTimestamp, 0).Format("2006-01-02 15:04:05"), time.Unix(endTimestamp, 0).Format("2006-01-02 15:04:05"))
    if err != nil {
        return fmt.Errorf("ID[%d]: %w", startTimestamp, err)
    }
    return web.Respond(ctx, w, employeeHistory, http.StatusOK)
}

Получение всех исторических данных

Проверьте запрос SQL (db/employees/employees_history.go), чтобы получить все исторические данные:

// GetAllHistory returns the complete history of a given employee.
func GetAllHistory(ctx context.Context, db *sql.DB, id uint) ([]models.EmployeeHistory, error) {
    q := `
    SELECT id, first_name, last_name, salary, department, row_start, row_end
    FROM employees
    FOR SYSTEM_TIME ALL
    WHERE id = ?
    `
    employeeHistory := make([]models.EmployeeHistory, 0)
    rows, err := db.QueryContext(ctx, q, id)
    if err != nil {
        return employeeHistory, err
    }
    defer rows.Close()
    for rows.Next() {
        var employeeHist models.EmployeeHistory
        if err = readEmployeeHistory(rows,
            &employeeHist.Id,
            &employeeHist.FirstName,
            &employeeHist.LastName,
            &employeeHist.Salary,
            &employeeHist.Department,
            &employeeHist.RowStart,
            &employeeHist.RowEnd,
        ); err != nil {
            return nil, err
        }
        employeeHistory = append(employeeHistory, employeeHist)
    }
    return employeeHistory, nil
}

Давайте попробуем:

curl 'http://localhost:3000/v1/employee/1/history/all'

[
  {
    "id": 1,
    "first_name": "John",
    "last_name": "Doe",
    "department": "IT",
    "salary": 1234.56,
    "row_start": "2023-04-06T13:31:50.830757Z",
    "row_end": "2025-04-06T00:00:00Z"
  },
  {
    "id": 1,
    "first_name": "John",
    "last_name": "Doe",
    "department": "IT",
    "salary": 2500,
    "row_start": "2025-04-06T00:00:00Z",
    "row_end": "2038-01-19T03:14:07.999999Z"
  }
]

Мы видим, что первая зарплата Джона была зарегистрирована 06 апреля 2023 г. со значением 1234,56, а 6 апреля 2025 г. он получил повышение до значения 2500.

Получение исторических данных на определенный момент времени

Что, если мы хотим проверить зарплату Джона в определенный момент времени? У нас есть этот SQL-запрос:

// AtPointInTime returns the complete history of a given employee in a given
// point in time.
func AtPointInTime(ctx context.Context, db *sql.DB, id uint, timestamp string) ([]models.EmployeeHistory, error) {
    q := `
    SELECT id, first_name, last_name, salary, department, row_start, row_end
    FROM employees
    FOR SYSTEM_TIME
    AS OF TIMESTAMP ?
    WHERE id = ?
    `
    employeeHistory := make([]models.EmployeeHistory, 0)
    rows, err := db.QueryContext(ctx, q, timestamp, id)
    if err != nil {
        return employeeHistory, err
    }
    defer rows.Close()
    for rows.Next() {
        var employeeHist models.EmployeeHistory
        if err = readEmployeeHistory(rows,
            &employeeHist.Id,
            &employeeHist.FirstName,
            &employeeHist.LastName,
            &employeeHist.Salary,
            &employeeHist.Department,
            &employeeHist.RowStart,
            &employeeHist.RowEnd,
        ); err != nil {
            return nil, err
        }
        employeeHistory = append(employeeHistory, employeeHist)
    }
    return employeeHistory, nil
}

Используя такой веб-сайт, как Конвертер эпох, легко получить метку времени. Какова была зарплата Джона в 2024-04-06?

curl 'http://localhost:3000/v1/employee/1/history/1712411281'

[
  {
    "id": 1,
    "first_name": "John",
    "last_name": "Doe",
    "department": "IT",
    "salary": 1234.56,
    "row_start": "2023-04-06T13:31:50.830757Z",
    "row_end": "2025-04-06T00:00:00Z"
  }
]

1234,56 именно потому, что он еще не получил повышения.

Какова была зарплата Джона 06.06.2025?

curl 'http://localhost:3000/v1/employee/1/history/1749217681'

[
  {
    "id": 1,
    "first_name": "John",
    "last_name": "Doe",
    "department": "IT",
    "salary": 2500,
    "row_start": "2025-04-06T00:00:00Z",
    "row_end": "2038-01-19T03:14:07.999999Z"
  }
]

Это было 2500, потому что он получил повышение в 2025-04-06.

Получение исторических данных между датами

Чтобы проверить эту историческую информацию между двумя датами, у нас есть этот запрос:

// BetweenDates returns the complete history of a given employee in a given period.
func BetweenDates(ctx context.Context, db *sql.DB, id uint, startTimestamp, endTimeStamp string) ([]models.EmployeeHistory, error) {
    q := `
    SELECT id, first_name, last_name, salary, department, row_start, row_end
    FROM employees
    FOR SYSTEM_TIME
    FROM ? TO ?
    WHERE id = ?
    `
    employeeHistory := make([]models.EmployeeHistory, 0)
    rows, err := db.QueryContext(ctx, q, startTimestamp, endTimeStamp, id)
    if err != nil {
        return employeeHistory, err
    }
    defer rows.Close()
    for rows.Next() {
        var employeeHist models.EmployeeHistory
        if err = readEmployeeHistory(rows,
            &employeeHist.Id,
            &employeeHist.FirstName,
            &employeeHist.LastName,
            &employeeHist.Salary,
            &employeeHist.Department,
            &employeeHist.RowStart,
            &employeeHist.RowEnd,
        ); err != nil {
            return nil, err
        }
        employeeHistory = append(employeeHistory, employeeHist)
    }
    return employeeHistory, nil
}

Какова была зарплата Джона между 06.04.2023 и 02.01.2024, учитывая, что ему повысили зарплату 06.04.2024?

curl 'http://localhost:3000/v1/employee/1/history/1680788881/1704203281'

[
  {
    "id": 1,
    "first_name": "John",
    "last_name": "Doe",
    "department": "IT",
    "salary": 1234.56,
    "row_start": "2023-04-06T13:31:50.830757Z",
    "row_end": "2025-04-06T00:00:00Z"
  }
]

Ага, 1234,56.

Дополнительные темы

Создание документации Swagger

Проверив наш *Makefile *, мы обнаружили следующие цели:

$ make help

Usage: make [target

  help                   shows this help message

  mariadb-console        launches mariadb local database console

  test-mariadb-console   launches mariadb test database console

  create-migration       creates a migration file

  test                   runs unit tests

  coverage               run unit tests and generate coverage report in html format

  test-db-up             starts test database

  int-test               runs integration tests

  vet                    runs go vet

  lint                   runs linter for all packages

  vul-setup              installs Golang's vulnerability check tool

  vul-check              checks for any known vulnerabilities

  swagger                generates api's documentation

  swagger-ui             launches swagger ui

  run                    runs the application

  stop                   stops all containers]

Создать документацию Swagger довольно просто. В папке «doc» у нас есть:

doc/doc.go

// Employees API
//
//   A sample RESTful API to manage employees.
//      Host: localhost:3000
//      Version: 0.0.1
//      Contact: Tiago Melo <tiagoharris@gmail.com>
//
//      Consumes:
//      - application/json
//
//      Produces:
//      - application/json
//
// swagger:meta
package doc

И doc/api.go

// Copyright (c) 2023 Tiago Melo. All rights reserved.
// Use of this source code is governed by the MIT License that can be found in
// the LICENSE file.
package doc


import "github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial/db/employees/models"


// swagger:route GET /v1/employees employees GetAll
// Get all current employees.
// ---
// responses:
//      200: getAllCurrentEmployeesResponse


// swagger:response getAllCurrentEmployeesResponse
type GetAllCurrentEmployeesResponseWrapper struct {
    // in:body
    Body []models.Employee
}


// swagger:route GET /v1/employee/{id} employee GetById
// Get a current employee by its id.
// ---
// responses:
//
//  200: employee
//  400: description: invalid id
//  404: description: employee not found
//
// swagger:parameters GetById
type GetEmployeeByIdParamsWrapper struct {
    // in:path
    Id int
}


// swagger:response employee
type EmployeeResponseWrapper struct {
    // in:body
    Body models.Employee
}


// swagger:route POST /v1/employee employee Create
// Create an employee.
// ---
// responses:
//
//  200: employee
//
// swagger:parameters Create
type PostEmployeeParamsWrapper struct {
    // in:body
    Body models.NewEmployee
}


// swagger:route PUT /v1/employee/{id} employee Update
// Updates a current employee.
// ---
// responses:
//
//  200: employee
//  400: description: invalid id
//  404: description: employee not found
//
// swagger:parameters Update
type PutEmployeeParamsWrapper struct {
    // in:path
    Id int
    // in:body
    Body models.UpdateEmployee
}


// swagger:route DELETE /v1/employee/{id} employee Delete
// Deletes a current employee.
// ---
// responses:
//
//  204: description: no content
//  400: description: invalid id
//
// swagger:parameters Delete
type DeleteEmployeeParamsWrapper struct {
    // in:path
    Id int
}


// swagger:route GET /v1/employee/{id}/history/all history GetAllEmployeeHistoryById
// Get all historical data about an employee with a given id.
// ---
// responses:
//
//  200: employeeHistory
//  400: description: invalid id
//
// swagger:parameters GetAllEmployeeHistoryById
type GetAllEmployeeHistoryByIdParamsWrapper struct {
    // in:path
    Id int
}


// swagger:route GET /v1/employee/{id}/history/{timestamp} history GetAllEmployeeHistoryAtPointInTime
// Get historical data about an employee with a given id at a given point in time.
// ---
// responses:
//
//  200: employeeHistory
//  400: description: invalid id
//  400: description: invalid timestamp
//
// swagger:parameters GetAllEmployeeHistoryAtPointInTime
type GetAllEmployeeHistoryAtPointInTimeParamsWrapper struct {
    // in:path
    Timestamp int
}


// swagger:route GET /v1/employee/{id}/history/{startTimestamp}/{endTimestamp} history GetAllEmployeeHistoryBetweenDates
// Get historical data about an employee with a given id between dates.
// ---
// responses:
//
//  200: employeeHistory
//  400: description: invalid id
//  400: description: invalid start timestamp
//  400: description: invalid end timestamp
//
// swagger:parameters GetAllEmployeeHistoryBetweenDates
type GetAllEmployeeHistoryBetweenDatesParamsWrapper struct {
    // in:path
    StartTimestamp int
    // in:path
    EndTimestamp int
}


// swagger:response employeeHistory
type EmployeeHistoryResponseWrapper struct {
    // in:body
    Body []models.EmployeeHistory
}

Тогда бежим:

make swagger-ui

Проверьте свой браузер по адресу http://localhost/:

No alt text provided for this image

Проверить наличие известных уязвимостей

$ make run vul-check

Запустить линтер

$ make lint

Заключение

Временные таблицы – это простой способ извлечения исторических данных, упрощающий путешествие во времени.

Проверьте здесь поставщиков, которые его реализуют.

Скачать исходный код

Здесь: https://github.com/tiagomelo/docker-mariadb-temporal-tables-tutorial.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE