Обзор GORP — ORM для языка Go

в 11:18, , рубрики: golang, Программирование, метки:

В предыдущей статье (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

Источник

Поделиться

* - обязательные к заполнению поля