Основная цель проектов – зарабатывать деньги. Проект над которым мне довелось работать, не стал исключением. Я разработчик компании Колёса Крыша Маркет и сегодняшний пост будет посвящен тому, как мы дифференцировали цены на платные услуги на нашем “classified”. Наша компания разрабатывает 3 продукта, каждый под 3 платформы – web, android и ios. Пользователи могут применять к объявлениям различные платные услуги, например, платное продление срока жизни объявления или размещение в блоке горячих предложений. Когда меня привлекли к этому проекту, у меня в голове еще до начала обсуждения держалась мысль, что за дифференцированные цены? Дифференцированная цена — цена формирование которой зависит от характеристик объявления (регион, марка, модель, год и т.д.). Перед командой стояла задача увеличить средний чек. Было принято решение “запилить” фичу, содержащую в себе функционал о котором дальше и пойдет речь. Смысл фичи был в том, что через админ-панель мы сможем изменять цену на любую платную услугу, опираясь на разные параметры. На момент начала разработки мы уже имели микросервис написанный на Go. Сайты и приложения общались с ним через клиент, отправляя POST-запросом объект объявления, а далее, получив в ответ цены на какие-либо платные услуги, рендерили их пользователю. В процессе изучения микросервиса выяснилось, что цены, которые отдаются пользователю, были захардкожены, то есть цена для размещения объявления в горячих предложениях описывалась переменной “hot”: 300, а результат ответа выглядел примерно так: Было принято решение заняться важным рефакторингом и избавиться от хардкода в пользу метода, который бы отдавал дифференцированную цену на услугу. Процесс разработки мы разделили на несколько этапов: Изначально, согласно ТЗ, менеджер продукта хотел иметь возможность устанавливать цену на платную услугу из административной панели и это было одной из преимущественно важных задач. Передо мной встал вопрос, в каком формате хранить данные. Я принял решение забить базу данных “правилами”, то есть, если категория объявления “Авто” и регион “Алматы”, то применяем для объявления следующею цену. Осталось разобраться с базой данных. Первое, что пришло на ум, это база данных MySQL, где будет храниться таблица с правилами для цен. Например: Согласно ТЗ было необходимо устанавливать цену исходя из региона и категории. Но, зная, как все эти “хотелки” работают, подумал, что цену понадобится менять не только для категории и региона, а еще и для определенной модели авто или ещё по какому-либо признаку.  В общем, MySQL отпал как вариант, и выбор пал на MongoDB, которая бы обеспечила нам широчайшие возможности динамического масштабирования и умела работать с массивами данных, без которых правила были бы бесполезны.  Админ-панель вещь не сложная, у нее должен был быть стандартный функционал CRUD, то есть добавление, редактирование, удаление и отображение этих правил в удобном для чтения виде. Написали всё это дело мы на phalcon, так как эта админ-панель была лишь частью основной админки для сайта, работающего на phalcon. Также написали функционал в API, который валидировал и сохранял наши правила в коллекцию MongoDB. Json-объект объявления выглядел вот так: Последним этапом нашей разработки был этап написания кода, который умел бы работать со всеми этими правилами и возвращал бы на запрос ответ с дифференцированной ценой. Как раньше: Как сейчас: А где же рефакторинг и где же дифференцированные цены? А вот. Эти захардкоженные переменные в коде в итоге остались в качестве значений по-умолчанию.  Алгоритм отдачи цен раньше работал так: Сейчас все работает также, за исключением того, что перед возвратом ответа пользователю теперь идёт поход в метод дифференциации цены.  Первый подход к решению задачи, был следующим.  В момент формирования массива с ценами для каждого из его элементов, то есть услуги, был дополнительный поход в MongoDB. Метод “getPriceForService” возвращал цену и принимал следующие аргументы: Помните, выше я написал, что есть коллекция с возможными правилами регулирования цен, они здесь и понадобились. Чтобы не проходить по каждому из полей объекта, была сделана выборка только из возможных правил. На следующем скрине, мы видим, процесс формирования запроса в MongoDB. В конечном итоге мы получили запрос, который оставалось лишь выполнить и посчитать новую цену на услугу, используя полученный коэффициент. Однако, после релиза этого кода в production, наша MongoDB умерла из-за того, что при одном запросе в микросервис он отдает результаты для всех услуг, а я вызываю метод при формировании массива для каждого элемента. То есть, я увеличил нагрузку на MongoDB в 7-8 раз и в поте лица занялся переписыванием своего кода. Информация к сведению: Для работы с MongoDB, мы использовали mgo, которая позволяет с легкостью строить запросы в базу данных. Тем же вечером, поизучав код заново, я решил вызывать этот функционал в самый последний момент, то есть перед тем, как отдать результаты клиенту. Я постучусь в этот же метод, только чуть-чуть переписанный. Переписанный метод стал принимать уже не название услуги, а список услуг с ценами готовый к отдаче. Как и раньше, получив запрос и выполнив его, мы получаем данные с правилами для каждой услуги и коэффициент на который нужно умножить старую цену. Этот подход исключил все лишние походы в MongoDB, тем самым мы перестали нагружать нашу базу и получили дифференцированные цены :) (profit). Backend Developer