Типичные задачи: добавление нового поля
Одна из типичных задач при развитии сервиса — это добавление нового поля для сущностей 1С, которое также нужно прокинуть в текущий сервис и позже отдавать потребителям.
Что нужно сделать, чтобы новое поле считывалось от сущностей 1С, записывалось в базу и отдавалось вместе с нужными сущностями при запросе к сервису?
Пример: для транспортных операций в 1С добавилось некое поле "РегистрационныйНомер".
Поле строкового типа и содержит произвольный набор символов, например — 2023-ОКТ-21_АБВ-1-2-3.
Тогда для его учёта нужно:
1. Добавить поле в описание модели транспортных операций
Добавить в описание модели transport_operations.go объявление новой колонки:
field.Text(`registration_number`).
Optional().
Nillable().
StructTag(`json:"registration_number,omitempty" cargo:"РегистрационныйНомер" transform:"trim,sanitize"`).
Comment(`1С:РегистрационныйНомер`),
Такое дополнение означает:
- Добавляется (в таблицу транспортных операций) поле типа text;
- Колонка имеет название
registration_number; - Значение опционально (его не нужно указывать при создании такой модели данных);
- Значение может быть пустым (
NULLABLE); - Для поля в сгенерированной структуре объявить тег, который содержит:
- Название поля при кодировании в JSON —
registration_number(omitemptyпозволяет не выводить поле, если значение пустое) - Тег
cargoуказывает на полеРегистрационныйНомериз 1С, значение которого будет браться для текущего поля при интеграции; - Тег
transformсодержит две функции для преобразования взятого из 1С значения —trimобрежет пробелы по краям строки,sanitizeуберёт символ¶;
- Название поля при кодировании в JSON —
- Добавится комментарий к создаваемой колонке.
Сгенерированная модель находится в internal/generated/models/transportoperation.go.
После запуска команды make ent в модель будет добавлено новое сгенерированное поле:
type TransportOperation struct {
// ...
// 1С:ИнструкцияПоСдачеПорожнего
RegistrationNumber *string `json:"delivery_note,omitempty" cargo:"ИнструкцияПоСдачеПорожнего" transform:"trim,sanitize"`
// ...
}
При старте сервиса после подключения к базе данных будет выполнен SQL-запрос для актуализации соответствующей таблицы:
alter table public.transport_operations
add registration_number text;
comment on column public.transport_operations.registration_number is '1С:РегистрационныйНомер';
2. Добавить поле в конфигурацию сборщика
Конфигурация находится в файле:
internal/data/repository/transport_operation/config_by_filter.go
Поле нужно добавить в поля запроса SelfFields:
SelfFields: []string{
// ...
transportoperation.FieldRegistrationNumber,
// ...
}
Также поле нужно добавить в функцию преобразования данных, то есть mapper, для транспортных операций
это функция mapSelf() в файле:
internal/data/repository/transport_operation/mappers.go
Поле:
func mapSelf(
item *models.TransportOperation,
prefetched collectorTypes.PrefetchedByModelType,
) collectorTypes.ItemMapped {
// ...
return collectorTypes.ItemMapped{
// ...
transportoperation.FieldRegistrationNumber: item.RegistrationNumber,
// ...
}
}
Поле будет запрашиваться и выводиться для базовой структуры.
Однако, для его появления в данных через API этих действий недостаточно.
3. Добавить поле в Protobuf-схему
Схема находится в файле:
api/domain/v1/common.proto
Поле:
message TransportOperationItem {
// ...
optional string registrationNumber = 28;
}
Важно задать число, которого ещё не было указано и не менять числа для существующих полей. Иначе, это может привести к невозможности межсервисного взаимодействия после обновления контейнера сервиса.
Чтобы сгенерированные структуры данных обновились, нужно выполнить команду:
make api
4. Добавить поле в функции для вывода данных через API
Функция для транспортных операций называется TransportOperationItem() и находится в файле:
internal/service/transforms/collect/transport_operation.go
Поле добавляется так:
func TransportOperationItem(item map[string]any) *v1.TransportOperationItem {
e := extractor.MustMake(item)
return &v1.TransportOperationItem{
// ...
RegistrationNumber: e.Get(transportoperation.FieldRegistrationNumber).ToString(),
// ...
}
}
5. Поправить тесты
Часть тестов может сломаться, следует проверить файлы:
tests/datasets/transport_operations/datasets.json
tests/datasets/transport_operations/datasets.go
tests/api/transport_operations.go
tests/api/transport_operations_expanded.go
6. Выполнить проверки
Запустить тесты можно через
make test
Проверить корректность написания кода с помощью линтера
make lint
Оба этих шага обязательно должны проходить успешно, иначе будут упавшие билды и созданный Merge Request не получится отправить на слияние в master.
7. Запустить локально (необязательный шаг)
Для запуска должен быть поднят Postgres, для этого в каталоге сервиса infra в терминале можно запустить:
./deploy.sh postgres
Это запустит контейнер с PostgreSQL. Позже, аналогичным образом можно будет удалить контейнер из локального Docker-а:
./undeploy.sh postgres
Для локального запуска можно создать файл configs/.env.local и указать в нём значения для локального окружения:
ENV=local
LOG_LEVEL=warn
SENTRY_ENABLED=false
SENTRY_DSN=
SENTRY_LEVEL=warn
SENTRY_FLUSH_TIMEOUT=2s
METRICS_ADDRESS=localhost:8125
METRICS_MUTE=true
SERVER_HTTP_ADDR=0.0.0.0:8030
SERVER_HTTP_TIMEOUT=120s
SERVER_GRPC_ADDR=0.0.0.0:9030
SERVER_GRPC_TIMEOUT=120s
DATA_DATABASE_MIGRATE=hard
DATA_DATABASE_DEBUG=true
AUTH_JWT_SECRET=CHANGE_THIS_JWT_SECRET
POSTGRES_USER=postgres
POSTGRES_PASS=CHANGE_THIS_POSTGRES_PASSWORD
POSTGRES_DB=domain
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
STORAGE_PATH=./storage
CARGO_INTEGRATIONS_PATH=./tmp/export
CLIENT_GRPC_NOTIFICATIONS_ENDPOINT=localhost:9020
CLIENT_GRPC_NOTIFICATIONS_TIMEOUT=10s
CLIENT_GRPC_AUTH_ENDPOINT=localhost:9010
CLIENT_GRPC_AUTH_TIMEOUT=10s
Сервис можно запустить командой:
go run ./cmd/server/... --dotenv=.env.local
Либо, если установлен GoLand, можно запустить отладку с помощью конфигурации:
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="server.local" type="GoApplicationRunConfiguration" factoryName="Go Application">
<module name="domain" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="--dotenv=.env.local" />
<EXTENSION ID="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
</ENTRIES>
</EXTENSION>
<kind value="PACKAGE" />
<package value="domain/cmd/server" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/cmd/server/main.go" />
<method v="2" />
</configuration>
</component>