• Кто нибудь применял или видел generics в серьезных Go проектах?

    Одно из самых полезных применений дженериков, которое у меня было — это написание врапперов, которые добавляют строгую типизацию в обработку чего-либо и уменьшают бойлерплейт. Например, обработка http-запросов. Обычно используется роутер и в него регистрируются хендлеры для разных путей, например:
    router := httprouter.New()
    
    router.POST("/api/products", productsHandler.Handle)
    router.POST("/api/get_free_slots", getFreeTimeSlotsHandler.Handle)
    router.POST("/api/create_visit", createVisitHandler.Handle)


    При этом, функция Handle у каждого хендлера это обычно что-то типа func(w http.ResponseWriter, r *http.Request)
    Хендлер уже внутри себя сам читает тело, парсит его из json, потом формирует ответ, итд.

    Хотелось добавить сюда типизацию и какую-то структуру, поэтому я написал
    простой враппер

    type Validatable interface {
    	Validate() error
    }
    
    func Wrap[Req Validatable, Res any](fn func(ctx context.Context, req Req) (Res, error)) httprouter.Handle {
    	handler := func(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
    		writeError := func(statusCode int, err error) {
    			writer.WriteHeader(statusCode)
    			_, _ = writer.Write([]byte(err.Error()))
    		}
    
    		var req Req
    
    		bytes, err := io.ReadAll(request.Body)
    		if err != nil {
    			writeError(http.StatusBadRequest, err)
    			return
    		}
    
    		if err := json.Unmarshal(bytes, &req); err != nil {
    			writeError(http.StatusBadRequest, err)
    			return
    		}
    
    		if err := req.Validate(); err != nil {
    			writeError(http.StatusBadRequest, err)
    			return
    		}
    
    		resp, err := fn(request.Context(), req)
    		if err != nil {
    			writeError(http.StatusInternalServerError, err)
    			return
    		}
    
    		respBytes, err := json.Marshal(resp)
    		if err != nil {
    			writeError(http.StatusInternalServerError, err)
    			return
    		}
    		writer.WriteHeader(http.StatusOK)
    		_, _ = writer.Write(respBytes)
    	}
    
    	return handler
    }



    Он берет на себя весь бойлерплейт по парсингу и валидации запроса, формированию ответа, работы с кодами, итд. Причем, через стандартный errors.Is иногда добавлял сюда еще возможность из хэндлера указать врапперу, какой http-код ответа отдавать. Обычно все функции враппера делал под конкретную задачу в конкретном проекте (можно, например, не только json тела парсить, но и урл и хедеры, итд).

    Использование враппера выглядит так.
    Мы пишем хэндлер, используя конкретные типы

    type Handler struct {
    	userService *usersvc.Service
    }
    
    type Request struct {
    	UserID int64 `json:"user_id"`
    }
    
    func (c Request) Validate() error {
    	if c.UserID == 0 {
    		return errors.New("empty user id")
    	}
    	return nil
    }
    
    type Response struct {
    	Name string `json:"name"`
    	Age  int    `json:"age"`
    }
    
    func New(userService *usersvc.Service) *Handler {
    	return &Handler{userService: userService}
    }
    
    func (h Handler) Handle(ctx context.Context, req *Request) (*Response, error) {
    	user, err := h.userService.UserByID(ctx, req.UserID)
    	if err != nil {
    		return nil, err
    	}
    
    	return &Response{
    		Name: user.Name,
    		Age:  user.Age,
    	}, nil
    }



    Через dependency injection даем хэндлеру все источники данных, изолируем хэндлер в отдельном пакете, четко прописываем ему типы Request и Response, пишем валидацию для запроса. Открыв хэндлер, разработчик сразу видит его контракты, весь бойлерплейт во враппере, остается написать только логику.

    В итоге использование такого хэндлера+враппер выглядит так:
    getUserHandler := getuser.New(userSvc)
    
    router := httprouter.New()
    
    router.POST("/api/get_user", wrapper.Wrap(productsHandler.getUserHandler))


    Причем, тут даже не видно, что Wrap() имеет дженерик-параметры, потому что гошка самостоятельно выводит эти параметры из типов Request и Response из хэндлера и проверяет, что у Request есть метод Validate.

    Подобные дженерик-врапперы применял в нескольких разных задачах. Кроме обработки запросов еще была система для запуска фоновых джоб по обработке данных, нужно было положить джобы в один слайс, но при этом, чтобы контракты джоб были типизированы. Поэтому враппером приводил конкретные типы структур из джоб к более универсальному виду с типами в interface{}. Получилось, что и типы все на компиляции проверяются и могу положить все джобы в одну коллекцию.
    Ответ написан
    Комментировать
  • Как связать React + GoLang?

    sergey-gornostaev
    @sergey-gornostaev
    Седой и строгий
    Так же, как бэкенд на любом языке и фронтенд на любом фреймворке - по протоколу http.
    Ответ написан
    Комментировать
  • Как правильно регулировать связанность компонентов?

    @calculator212
    структура, которая будет использоваться во множестве других пакетов(например, отправка отп кода через email или смс).
    Это можно вынести либо в отдельный проект, либо помещать такое в папку pkg, в которой обычно и лежат переиспользуемые пакеты.
    Где правильно хранить интерфейс с методами этой структуры?
    Создайте отдельный пакет в том месте где вам удобно, главное чтобы не получилось циклических зависимостей в будущем. Конкретно для вашего случая, можете поискать как реализуют фабрику в го, мне кажется это должно вам помочь.
    Ответ написан
    Комментировать
  • Можно ли заменить javascript языком dart/flutter?

    @deliro
    Хочется уже какой-то стабильности

    Для этого есть Elm на фронтенде и Rust на бэкенде
    Ответ написан
    Комментировать
  • Можно ли заменить javascript языком dart/flutter?

    Deita
    @Deita
    Встречайте, TypeScript!
    6491411ee1ef2823717372.png
    Ответ написан
    Комментировать
  • Как сделать анимацию курсора под текстом?

    neuotq
    @neuotq
    Прокрастинация
    У вас другие слои перехватывают события poiner-events, поэтому конвас как бы не видит их.
    Для этого, например, можно для main__section отключить : pointer-events: none;, а потом для нужных элементов включать, в данном случае для canvasFluid: pointer-events: all;

    PS и помним что такой эффект крайне ресурсоемкий, советую протестировать на "слабых" устройствах, на многих будет МНОГО меньше 60 fps, да даже меньше 10.
    Ответ написан
    4 комментария
  • Возможно ли расположить input'ы в форме в рандомной последовательности?

    sergey-gornostaev
    @sergey-gornostaev
    Седой и строгий
    Да, возможно.
    Ответ написан
    Комментировать
  • Как искать значение в сбалансированном бинарном дереве?

    jcmvbkbc
    @jcmvbkbc
    "I'm here to consult you" © Dogbert
    Как искать значение в сбалансированном бинарном дереве?

    Так же, как и в несбалансированном. Сбалансированность на стратегию поиска никак не влияет. Влияет логика построения дерева.

    Чтобы создать из этого сбалансированное дерево я беру элементы с индексом floor(arr.length / 2)

    С такой логикой элементы равные данному могут попасть как в левое поддерево, так и в правое. Чтобы найти их все нужно будет сканировать оба поддерева, если корень равен искомому числу.

    Ну да я нашел первый на самом верху дерева, ну а потом? Левый узел меньше тройки а правая больше, но под правым узлом есть еще тройка. По какой логике надо искать ?

    Если текущий корень больше искомого -- идти в левое поддерево, Если меньше -- идти в правое. Если равен -- то найдено, после чего идти в оба поддерева.
    Ответ написан
    3 комментария
  • Как наладить связи в бд?

    mayton2019
    @mayton2019
    Bigdata Engineer
    famsssss это ETL с нормализацией. Задача - типичная.
    У тебя должен быть примерно такой план действий.

    1) Таблицы vak, ring, mdb, cat, spec нужно загрузить в БД как есть. В денормализованном виде. Если они лежат в excel - то сохранить их как CSV формат. Далее дело техники. Можете задать другой вопрос в qna по поводу того как их грузить.

    2) Надо нарисовать реляционную модель. Это примерно то что ты рисуешь в картинке но нужно рисовать от сущности-связи а не от того какие файлы даны. Например есть сущность ISNN. У нее есть какие-то атрибуты. Они возможно опциональные. Но они должны быть перечислены. Далее - другие сущности. Потом определяем связи между ними. Например если многим spec соотвествует один ISNN - то тип связи будет многие к одному. Бывает такое что между двумя сущностями связи многие-ко-многим. Как прямоугольная матрица где по горизонтали одна сущность а по вертикали - другая и на пересечении стоит YES когда связь в наличии. Это тоже можно. Это делается через промежуточную таблицу. Связи бывают рекурсивные (таблица может указывать сама на себя). И в РМ могут быть циклы и петли. Это тоже допускается. Просто в этом случае между сущностями будут несколько вариантов как их джойнить и все варианты верны.

    3) После того как Реляционная Модель (РМ) определена - в нее можно загружать данные. Можно грузить через INSERT/UPDATE/MERGE. И если возможностей не хватит то можно брать хранимые процедуры на Postgres. Но обычно мне хватало и SQL. В крайнем случае можно брать языки типа Python, Ruby e.t.c. если например доменная модель ооооочень сложная и надо какие-то делать неочевидные поиски по коллекциям или работать с JSON/XML но у тебя вроде все атомарно и лежит просто в ячейках. Должно хватить SQL.

    4) Последняя таблица izdanya - по смыслу является отчетом из основной модели. Я настаиваю именно на таком подходе. Физически - это может быть view или таблица неважно. Главное что она - вторична по отношению к модели.
    Ответ написан
    Комментировать
  • Почему не работает form.addEventListener?

    HardBot
    @HardBot
    back-end, front-end developer
    document.addEventListener('DOMContentLoaded', () =>{
        const sigInBlock = document.getElementById('sigInBlock');
        let inputEmail = (document.getElementById("inputEmail") as HTMLInputElement);
        const form = (document.getElementById('form') as HTMLFormElement);
    
        const userPage = document.getElementById('userPage');
        const userName = document.getElementById('userName');
        const btnEdit = document.getElementById('btnEdit');
        const btnFollow = document.getElementById('btnFollow');
        const inputLenta = document.getElementById('inputLenta');
        const myLenta = document.getElementById('myLenta');
        const btnSend = document.getElementById('btnSend');
        
        const run = (() =>{
            form.addEventListener('submit', (e) =>{
                e.preventDefault();
                console.log('hi')
                console.log(inputEmail.value)
                //const response = await fetch(' http://146.185.154.90:8000/blog/' + inputEmail.value);
            })
        })();
    })
    Ответ написан
    Комментировать
  • Как оплатить GitHub CoPilot?

    kellas
    @kellas
    веб-разработчик
    • Понадобится новый(не российский) акк на github, я регал на всякий случай через германскую vpn, но кажется принадлежность к РФ github определяет по стране указаной в Billing info.
    • При добавлении способа оплаты указал как страну - Германию и там какой-то левый адрес.
    • Привязал виртуальную карту от pyypl.com

    К моему основному аккаунту зарубежные карты хоть и привязываются но оплатить github услуги не дает - пишет про санкции в отношении РФ.

    В vscode сначала нужно разлогиниться со старого аккаунта , потом на предложение от copilot на sign in - войти через браузер с новым github аккаунтом.
    635064d0b7479953051906.png
    Ответ написан
    3 комментария
  • Какой можно применить алгоритм для хранение индекса для 50 миллиардов записей в golang?

    mayton2019
    @mayton2019
    Bigdata Engineer
    Немного бухгалтерии. Если взять по максимуму.

    Размер одной записи должен быть порядка 60 + 32 +8 = 100 символов (байт для простоты)

    При 50 млрд записей объем хранимых данных должен быть порядка

    50 000 000 000 * 100 = 5 000 000 000 000 = 5 триллионов байт.

    В дисковом хранении это будет примерно 4.5 терабайта. Задачка для in-memory неподъемная. Нужен диск + мемори.

    Если я там где-то ошибся в расчетах - то только в средних значениях. Если подставить не максимумы а среднее - то цифры будут поскромнее. Но в любом случае - многовастенько для покупки памяти.

    Вобщем нужна дисковая БД которая встраивается в приложение. На требование менеджеров которые запретили использовать БД забейте. Они ничего не понимают. Делайте БД встраиваемую в приложение. В качестве таких (встраиваемых систем можно поробовать) LevelDb, BerkeleyDb, RocksDb. Они поддерживают индекс класса B+Tree и это даст возможность искать группы записей по одному ID. Для этого класса систем любую запись можно найти за 4-5 дисковых IOPS. Если какдый IOPS стоит 15 мс (это я так мерял свой собственный магнитный HDD) то любой поиск группы ключей для TTFB будет порядка 15 * 5 = 75 милисекунд. Ну если вы поставите SSD - то быстрее.

    По поводу предложений хранить в файлах. До того как обсуждать это - надо уяснить требования по времени отклика. Сколько секунд вы согласны ждать - насколько можно и партицировать (или шардировать ваш файл).
    В простейшем случае мы делим большой CSV файл на 512 partitions по хешу от ID и получаем соотв время фулл-скана всего файла поделенное на 512. Дальше - играйтесь с этим параметром партишенинга выводя его на доступный уровень отклика. Из недостатков - будет россыпь файлов. Надо почиать документацию на вашу файловую систему (ext4?) и тюнить ее так чтоб она не сдохла от такого числа inodes.

    Я поддержу оба сценария. И с встраиваемой БД и с файлами. Но с БД надежнее т.к. есть транзакции а файлы у вас могут быть в крешнутом состоянии долго. И вы об этом ничего знать не будете.

    По поводу Parquet. Не взлетит. Скорее всего индекс по данному типу файла - это совсем не то что вкладывают туда релационные системы. Обычно Parquet/Orc/Delta вкладывают в индекс смысл - отбрасывания тех полосок данных (stripes) которые бесполезны при чтении всего файла. Такой индес - обычно просто либо range-признак либо карта Блума. И в случае с range - дает эффект на сортированных данных. Для прочих - будет бесполезно т.к. фулл-скан все равно обеспечен. А если фулл-скан то зачем тогда вообще индекс.

    Вобщем для дизайна архитектуры нам нужны цифры. Средние длины по колонкам. И я-бы еще запросил кардинальность по полю ID.
    Ответ написан
    7 комментариев
  • Как правильно реализовывать прелоадеры?

    0xD34F
    @0xD34F Куратор тега Vue.js
    должен быть более лаконичный способ, чем плодить такие переменные

    Смотрите, где какие куски кода есть похожие, оформляете их в отдельный компонент. А что у них разного - будет параметрами этого компонента.

    Или это считается нормальной практикой?

    Да, считается. У говнокодеров.
    Ответ написан
    3 комментария
  • Как подключитЬся с помощью Golang к другой программе?

    1. Через сетевой протокол на API сторонней программы. (обычно используют JSON RPC, REST, GRPC)
    2. Через вызов клиента другой программы в командной строке из вашего приложения и парсинг вывода.
    3. Вызов функций SDK, предоставленного сторонней программой (обычно через cgo).

    Остальные способы обычно довольно геморойные и редко используются.
    Ответ написан
    Комментировать
  • Есть ли подводные камни в использовании ссылок Golang в горутинах?

    @Zolg
    но открыл для себя особенность, что если передавать ссылку в горутину, в горутине будет самое последнее значение переменной в цикле
    это не 'особенность', это основная фича ссылок. В любом ЯП.

    Сама переменная - это некоторая область в памяти, в которой записано то, что в этой переменной содержится.
    При передаче параметра по значению ('без ссылок') создается еще одна такая же область в памяти, куда копируется содержимое исходной переменной. И код вызванной функции работает уже со своей копией.

    При передаче параметра по указателю ('с сылками') копия переменной не создается, а в вызываемую функцию передается адрес (указатель, ссылка) области памяти в которой лежат оригинальная переменная. И вызванная функция работает уже с ним.

    Очевидно, что когда два куска кода одновременно могут писать читать в одно и то же место памяти - они мгновенно 'видят' изменения, вносимые каждым.

    И, естественно, в общем случае без дополнительных усилий это не thread/goroutine/etc. safe. Причем в случае, если сама переменная - что-то посложнее int, то эффект от такой одновременной работы может быть гораздо более занятным, чем просто изменение значения.
    Ответ написан
    2 комментария
  • Стоит ли использовать esbuild для сборки фронта сегодня?

    @deliro
    Я пару недель назад перевёл сборку фронта с rollup на esbuild. Время сборки упало с 13с до 200мс (x65 ускорение). У коллег на M1 это время стало вообще 40мс

    Из минусов заметил только отсутствие таргета ES5 (ну как заметил, об этом в доках пишут везде). Кстати, перевод таргета на ES6 уменьшил размер бандла незначительно (в основном за счёт сокращения function() в ()=>)

    Больше проблем нет
    Ответ написан
    2 комментария
  • Один проект или коммерческая разработка?

    RAFAILgaley
    @RAFAILgaley
    нет в жизни ничего хуже чем работа на заказ в россии
    лучше пилить один продукт и заниматься своими проектами
    Ответ написан
    Комментировать
  • Как скрыть див блок кнопкой в JS?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Можно вообще без JS, только стилями и невидимым чекбоксом, но разметка должна быть специфической:

    Ответ написан
    Комментировать
  • Почему так тяжело решать задачи?

    sergey-gornostaev
    @sergey-gornostaev
    Седой и строгий
    Это нормально. Мозг развивается практикой и процесс этот мало того, что не быстрый, так ещё и бесконечный.
    Ответ написан
    Комментировать