Статьи

Хранение базы данных в Go с Structable

Я не большой поклонник ОРМ. Не поймите меня неправильно, я не собираюсь начинать кампанию против них. Я знаю, что многие люди считают их отличным уровнем абстракции в своей базе данных. Но я обычно чувствую себя более продуктивно с написанием запросов, когда я могу просто написать SQL.

Хорошо, не всегда . Написание простых старых запросов CRUD (создание, чтение, обновление, удаление) является бессмысленной рутинной работой, эквивалентной очистке туалета.

Поэтому я написал небольшую библиотеку с именем structable, которая заботится о шаблонном CRUD для меня, но позволяет легко и просто создавать свои собственные запросы, когда пришло время выйти за рамки простых операций.

Примечание. Structable 3.0 является новым и нарушает обратную совместимость со Structable 1.0. Эта статья охватывает 3.0.

Структурная таблица

В основе Structable лежит простое средство отображения, которое читает аннотированную структуру и отображает ее в таблицу в реляционной базе данных. Я называю это «отображение структуры таблицы» (так называется StrucTable).

С помощью нескольких аннотаций я могу сделать простую карту:

type User struct {
    Id int `stbl:"id,PRIMARY_KEY,SERIAL"`
    Name string `stbl:"name"`
    Email string `stbl:"email"`
  NotMapped string // This will not be mapped to a DB record at all.
}

stblАннотацию говорит Structable о поле на структуры. Например, Nameстрока отображается в столбец с nameпомощью stblаннотации. Structable предполагает, что вы спроектировали свою базу данных таким образом, что вы сможете хранить строку в этом столбце.

IdПоле имеет некоторые дополнительные вещи в stblтеге:

Id int `stbl:"id,PRIMARY_KEY,SERIAL"`

PRIMARY_KEYКлючевое слово говорит structable , что поле Id является первичным ключом для таблицы. SERIAL(Или , AUTOINCREMENTесли вы предпочитаете) говорит Structable , что значение этого поля можно считать управлять базой данных.

Запись против рекордера

Структура выше является записью . Он содержит данные, но не знает, как что-то сделать с этими данными. Structable обеспечивает рекордер, чтобы что-то делать с данными.

В простейшей форме использование рекордера — это вопрос присоединения структуры записи к рекордеру :

db = //... we'll get to this in a minute.
dbFlavor = "postgres"
user := new(User)
userRecorder = structable.New(db, dbFlavor).Bind("users_table", user)

Рекордеру нужно несколько вещей. Во-первых, ему нужен дескриптор базы данных. Я вернусь к этому, но в двух словах, Structable использует мой любимый инструмент для работы с базами данных: белка .

Имея соединение с базой данных, мы можем связать () (прикрепить) конкретную таблицу и структуру к устройству записи. Теперь мы по существу сказали, что структура User хранится в users_tableи управляется нашей новой userRecorder.

Отсюда мы можем использовать рекордер для вставки, обновления, загрузки, тестирования или удаления записи:

user.Name = "Matt"
userRecorder.Insert()

Теоретически это похоже на шаблон проектирования, известный как DAO. Но самое элегантное выражение Structable найдено с использованием шаблона Active Record.

Использование Structable для активных записей

Активная запись — это объект записи, который является собственным рекордером. Мы можем легко это сделать с помощью Structable, добавив в нашу Userструктуру всего два небольших дополнения :

type User struct {
    structable.Recorder
    builder squirrel.StatementBuilderType

    Id int `stbl:"id,PRIMARY_KEY,SERIAL"`
    Name string `stbl:"name"`
    Email string `stbl:"email"`
}

// NewUser creates a new Structable wrapper for a user.
//
// Of particular importance, watch how we intialize the Recorder.
func NewUser(db squirrel.DBProxyBeginner, dbFlavor string) *User {
    u := new(User)
    u.Recorder = structable.New(db, dbFlavor).Bind("user_table", u)
    return u
}

Структура Userтеперь имеет анонимный structable.Recorder. В Go это позволяет нам «наследовать» методы Recorder. В целях расширения я также всегда сохраняю a squirrel.StatementBuilderTypeв своих записях, чтобы впоследствии я мог добавить новые методы базы данных.

Теперь давайте посмотрим на NewUserметод. Вместо создания внешнего рекордера он инициализирует свой внутренний аноним Recorder. Теперь я могу использовать экземпляры моей Userструктуры следующим образом:

user := NewUser(db, dbFlavor)
user.Name = "Matt"
user.Insert()

anotherUser := NewUser(db, dbFlavor)
anotherUser.Id = 123
anotherUser.Load() // This loads because Id is a PRIMARY_KEY field.

Краткое слово о подключении к базе данных

Вы можете взглянуть на Squirrel, чтобы лучше понять, как управлять соединениями. Это интуитивно понятно и очень мощно. Но вот как я настроил это для примеров выше:

func main() {

    // Boilerplate DB setup.
    // First, we need to know the database driver.
    driver := "postgres"
    // Second, we need a database connection.
    con, _ := sql.Open(driver, "dbname=structable_test sslmode=disable")
    // Third, we wrap in a prepared statement cache for better performance.
    cache := squirrel.NewStmtCacheProxy(con)

    // Create an empty new user and give it some properties.
    user := NewUser(cache, driver)
    user.Name = "Matt"
    user.Email = "matt@example.com"
}

По сути, я создаю новое соединение с базой данных Postgres (хотя оно отлично работает с MySQL и, возможно, с другими разновидностями БД). Затем я оборачиваю его в кэш операторов Squirrel, чтобы я мог прозрачно повторно использовать подготовленные операторы. Оттуда я отправляюсь на гонки.