В предыдущей статье (http://habrahabr.ru/post/178963/) я рассказывал как работать с базой данных на Go. В комментариях к посту мне посоветовали посмотреть две библиотеки ORM.
Вообще, если говорить о работе с базой данных в Go, то самым простым способом будет представление структуры данных в виде массива или словаря. Сериализация в этом случае будет очень простой, но работа с такими типами данных в коде не выглядит привлекательной. Конечно, можно добавить обертки и т. д, но если посмотреть на архитектуру любого подобного решения, то так или иначе вырисовывается ORM.
В обеих предложенных библиотеках я не нашел всего необходимого функционала.
Beedb (https://github.com/astaxie/beedb)
Beedb — отличная библиотека с обширным API. Используя beedb, мы сможем строить запросы, оперируя функциями Having, Limit, Where и т. д. (если мы не используем ORM, необходимо прописывать весь запрос к базе строкой). Прикладной программный интерфейс можно посмотреть по адресу https://github.com/astaxie/beedb/wiki/API-Interface.
GORP (https://github.com/coopernurse/gorp)
Gorp — небольшая библиотека, в API которой входит минимум функций:
- Get — извлекаем запись по ключу.
- Insert — добавляем новую запись.
- Delete — удаляем по ключу.
- Update — обновляем по ключу.
- Select — выборка произвольного числа объектов.
- Exec — выполнить произвольный запрос.
Библиотека позволяет работать с разными SQL диалектами, API значительно меньше, чем у Beedb.
Как мне показалось, Beedb намного сложнее в использовании. Задача, под которую выбиралась библиотека — создать единый вход для форумов “Сети Знаний” (чтобы пользователь мог авторизоваться на одном форуме по данным с другого). Напомню, движок http://hashcode.ru/ написан на Python.
Как мне кажется, главной проблемой при работе с базой данных в Go является не сложность занесения объектов, а сложность выборки. Весь код обрастает строками содержащими sql запросы, в которых тяжко разбираться (чего нет в Django). В добавок с отсутствием перегрузки функций в языке, код становится очень объемный.
Beedb при всем своем многообразии API не решает проблему. Для реализации я выбрал GORP. Далее я расскажу о нескольких “подводных камнях” этой библиотеки.
Главные файлы:
- dialect.go — содержит код зависимый от SQL диалектов.
- gorp.go — код библиотеки.
- gorp_test.go — крайне важный файл с тестами, в нем мы можем посмотреть примеры использования библиотеки.
Главной проблемой в работе с библиотекой послужило то, что она “проглатывает” ошибки. Напомню, в Go нет исключений. В таком случае (как и в языке C) вызываемая функция сообщает об ошибке в возвращаемом значение. GORP не возвращает ошибок во многих случаях с функцией Get (не возвращает объектов, но и ошибка равна nil). В случае если вы заподозрили что-то неладное, попробуйте использовать метод Select вместо Get.
Второй интересной особенностью стала работа с объектом time.Time. GORP использует отображение (рефлексию) для создания таблиц базы данных. За это отвечает файл с диалектами. Функция отвечающая за преобразование структуры в табличку:
func (d PostgresDialect) ToSqlType(val reflect.Type, maxsize int, isAutoIncr bool) string {
switch val.Kind() {
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Uint16, reflect.Uint32:
if isAutoIncr {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if isAutoIncr {
return "bigserial"
}
return "bigint"
case reflect.Float64, reflect.Float32:
return "real"
case reflect.Slice:
if val.Elem().Kind() == reflect.Uint8 {
return "bytea"
}
}
switch val.Name() {
case "NullableInt64":
return "bigint"
case "NullableFloat64":
return "double"
case "NullableBool":
return "smallint"
case "NullableBytes":
return "bytea"
}
if maxsize < 1 {
maxsize = 255
}
return fmt.Sprintf("varchar(%d)", maxsize)
}
Можно увидеть, что время будет записано как число, а не как timestamp. При работе с драйвером для PostgreSQL от lxn (https://github.com/lxn/go-pgsql) это вызвало проблемы с извлечением данных. Я добавил следующие строки во второй switch
:
case "Time":
return "timestamp with time zone"
Следующим моментом стала ошибка при извлечении нулевого времени. При записи в базу дописываются дополнительные элементы в конец строки, которые не соответствуют стандарту RFC3339, с помощью которого идет преобразования строки в объект времени. Это происходит только в том случае, если объект нулевой (IsZero возвращает true).
Еще один момент касается префикса базы данных. Как ни странно в диалекте Postgres он объявлен не экспортируемым и его невозможно задать.
В следующих статьях я расскажу вам об архитектуре единого входа на форумы.
Далее секция Q&A. Пожалуйста, задавайте ваши вопросы в комментариях к посту, на форуме ХэшКод c метками “Golang” и “Go” или в личных сообщениях. Лучшие вопросы будут в конце каждой статьи.
Пользователь sergeyfast спросил, как обрабатывать нулевые значения при извлечении данных из базы данных.
Это делается за счет специальных типов, объявленных в пакете sql. Типы:
- NullBool
- NullFloat64
- NullInt64
- NullString
Пример обработки нулевого значения:
func makeIntFromResultSet(rows * sql.Rows) interface{} {
var value sql.NullInt64
err := rows.Scan(&value)
HandleDataBaseError(err)
if value.Valid {
return value.Int64
}
return -1
}
Пользователь zuborg спрашивал как мы управляем временем жизни кэша.
Алгоритм прост: время и ревалидация.
Мы заносим данные в memcached на 15 минут. Данные в memcached — это не просто пара ключ — значение, мы храним их как двойной указатель. Для доступа к записи необходимо сначала извлечь ключ, и только затем данные. Все данные описываются мета-ключами. Мета-ключом выступает первичный (или уникальный) ключ таблицы в базе, а также сам SQL запрос. Когда происходит обращение к базе, мы добавляем данные в кэш и приписываем им мета-ключи Таким образом, мы можем ревалидровать одни и те же данные по разным ключам, например, удалить из кэша все данные, где встречается пользователь с ID.
Автор: DevExpert