Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!10.04.2023
Оновлення для go-global. Особливості рефлексії для типу map в Go.
Сьогодні зробив для go-global - нашого пакета для завантаження конфігурації з AWS Parameter Store - маленьку фічу. Пакет цей бере дані з Parameter Store. Там вони мають пласку форму “ключ-значення”, причому значення можуть бути тільки рядками. Але ми хочемо отримати їх у вигляді конфігураційної структури з вкладеністю та з типізацією. Для цього й існує go-global
.
Але річ у тім, що він не підтримував тип map
- тільки структури та, з недавнього часу, масиви. Структури це добре, коли всі ключі відомі. Але зʼявилася потреба завантажувати словник з невідомими ключами.
Здавалося б, закинути дані в map
- це ще простіше, ніж у структуру. Навіть не треба шукати поле за тегом. Але насправді це невеличке розширення функціонала призвело до серйозного перероблення та переосмислення всього підходу.
Як воно взагалі все працює: через рефлексію. В першій версії бібліотеки я крок за кроком спускався по конфігураційній структурі, поки не знаходив reflect.Value
, що відповідає місцю, куди треба записати параметр. А потім записував в нього значення. В принципі нічого складного там не було; найскладніше — це, мабуть, створення нових екземплярів обʼєктів, коли стикаєшся з нульовими вказівниками.
Але. Тип map
, як виявилось, все це ламає. Бо він в Go особливий. Елементи map
, не підлягають адресації. На них не можна зробити вказівника — це раз. Але також, як наслідок, при рефлексії неможливо отримати таке посилання на елемент, яке дозволяє запис. Елементи треба записувати спеціальною функцією SetMapIndex. Так що мій рекурсивний пошук місця призначення більш не підходив.
(Обурений екскурс. Ці приховані особливості всіляких типів — те, що я в Go понад усе не люблю. Абстракція “простих” типів тече. В нульовий слайс можна записувати, а в нульовий меп — ні. Оці проблеми з адресацією. І таке інше… Після того, як я нахвалюю Go, соромно початківцям про ці деталі розповідати.)
Спочатку хотів синтезувати якийсь замінник для “адреси” місця призначення. Може, повертати функцію-сеттер. Але так і не зміг розвинути цей підхід.
Натомість перегорнув весь алгоритм догори ногами. По-перше, тепер я спочатку будую з параметрів абстрактне дерево. Потім, замість пошуку кожного параметра окремо, я роблю обхід цього дерева в глибину, та наповнюю конфігурацію, починаючи з найглибших частин.
Що це дає: коли справа доходить до типу map
, то ми спочатку повністю наповнюємо значення кожного елемента, а потім записуємо його в map
функцією SetMapIndex
. Якось так. В результаті вийшло навіть не дуже складно, хоча без повного переосмислення я б до такої версії не прийшов.
Та, до того ж тепер я відокремив всю цю складну логіку від конкретно AWS Parameter Store, та її можна досконало протестувати. А в майбутньому, може, й додати інші сховища; взагалі для цього є Viper, але в нього філософія відрізняється від нашої.