Pull to refresh
23
0
Виктор Лова @nsinreal

Пользователь

Send message

А при чем тут redux? Нормальные люди давно используют redux-toolkit, где не нужно изгаляться.

Это в общем-то всегда так. Из-за того, что у вас есть какие-то там проблемы, всем остальным нужно ограничить свободу. Очень милая позиция.

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

Ну скройте количество за/против/просмотрено лично для себя, через stylish.

Я не соглашусь. Конечно, надо как-то решать проблему. Но есть разница между использованием существующего решения и написанием своего кастомного.

Вы же не пишите свою собственную бд.

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

В целом потому что тема очень сложная, а практика применения не очень хорошо наработанная.

Можно рассматривать сейчас топик "микросервисы vs монолит" как "c++ vs java". Примерно такая же байда. Или есть риск ногу отсрелить, или некоторые задачи будут решаться хуже.

@vstreltsov @codefun

Это была книжечка Patterns, Principles, and Practices of Domain-Driven Design

В ней вам будут интересны следующие главы:

  • "Chapter 6 Maintaining the Integrity of Domain Models with Bounded Contexts"

  • "Chapter 7 Context Mapping"

  • "Chapter 8 Application architecture"

  • "Chapter 11 Introduction to Bounded Context Integration"

  • "Chapter 12 Integrating via Messaging"

  • "Chapter 13 Integrating via HTTP with RPC and REST"

---

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

идея понятна - надо стараться делать как лучше, несмотря на плохие примеры в отрасли

Тут в общем какая хрень мною была обнаружена. Если внезапно микросервисы сделаны так, что ты начинаешь их ненавидеть, то это потому что тот, кто их делал - неопытен в этом деле или даже в программировании вообще.

Это довольно частая беда для разработки ПО. Практически с каждой технологией так. Просто микросервисная архитектура больше остального требует квалификации от архитектора (или команды, если архитектора нет).

Т.е. вы считаете что обычно у компаний 10-100 микросервисов

Да. Можно считать, что микросервис - это какая-то отдельная фича. Возьмите свой проект и посчитайте, сколько там фич.

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

и такие ситуации когда обрабатывают запрос 5 микросервисов по цепочке - скорее всего в 99% случаев результата плохого проектирования

Да, у меня такие мысли.

Такое решение (5 в цепочке) не имеет много пользы от микросервисов (а именно: независимые беклоги, устойчивость остальной системы при временном выпадении одного микросервиса etc).

Зато имеет от микросервисов кучу проблем (а именно: проблемы с транзакционностью, необходимость синкать данные, сетевые задержки, тяжелый дебаг, размазанность кода etc).

И в принципе, если у вас 5 микросервисов в цепочке, то получается, что вы поделили систему по "слоям", а не по "фичам"; да еще и с чертовски сильной связностью.

просто вы сами отметили, что спроектировать правильно почти никому не удаётся, потому-то и интересно - не становятся ли бест-практики для микросервисов своего рода недостижимым идеалом

Насколько я понял, бест-практики являются недостижимым идеалом, потому что никто о них не знает.

У вас не было в личной практике случаев перепроектирования и скажем упрощения архитектуры с урезанием числа микросервисов?

Немного было. В основном из-за очень дебильных ошибок. Было например такое, что команда почему-то решила одну сущность раскладывать в два микросервиса. Причем взаимозависимость была максимальная, все данные тупо редактировались вместе и не могли быть разъединены. Причин для этого серьезных не было, а проблем было очень много.

Понимаете, ваша мысль звучит вполне здраво, но те системы что я видел повсеместно нарушают это

Почему так происходит:

  1. Микросервисы пишут люди средние по рынку, а не гении. И солюшены тоже средние по рынку, а не с какими-то дикими нефункциональными ограничениями. Если солюшен с цепочкой в 5 микросервисов работает и фирма не банкротится — все будет крутиться так и дальше. Если в монолите лапша, но он тоже работает — все будет крутиться так и дальше.

  2. Опять же, есть много хайповодов, конечно, которые не соображают, что делают. Я, когда в первые познакомился с микросервисами в их исполнении - долго ненавидел концепцию. Голову на место мне вставила книжечка по DDD+микросервисам - показала, где именно есть место микросервисам

  3. Изначально микросервисы идут из мира огроменных солюшенов. Даже если разработчики гении, то огроменный солюшен нельзя выдержать качественным со всех точек зрения. Он обязательно будет ошибочно спроектированным, даже если на его проектирование затратили тысячи человеколет.

Разве есть хоть какое-то доказательство (назовём это "закон длины цепочки для микросервисов") что любая распределенная система из микросервисов может быть спроектирована таким образом что длина цепочки обработки запроса не превышает заданного N ? Причем независимо от бизнес-логики и прочих факторов. Т.к. то что вы говорите, неявно опирается на веру в подобного рода закон.

Нет такого доказательства. И нет, на ничего подобного я не опираюсь. Если я говорю о том, что нельзя выделять микросервис просто так, то это не значит, что его всегда можно выделить.

Вот смотрите. Есть такое правило. Надо писать код без goto. Нарушается? Нарушается! Раньше — больше. Сейчас — куда меньше, но все равно goto есть и нужен. Разве правило дурацкое? Да ни в коем случае, замечательное правило.

Пишите код без goto, делите микросервисы с учетом transaction boundary.

Netflix делал всё что мог чтоб сократить задержки, например client service discavery. А взять хотя бы distributed tracing? Вряд ли он возник из необходимости трасировать цепочки из 2 запросов. А значит такая ситуация длинных цепочек возникает нередко. То что такие штуки возникли, говорит о том что ваша идея расходится с реальностью.

Вдумайтесь только, у netflix >1000 микросервисов! Это же очень-очень-очень прилично. Distributed tracing в такой системе является необходимостью, даже если большинство цепочек из всего 1-2 микросервисов. И более того, в системе таких размеров написание distributed tracing - это просто капля в море затрат. Тем более, продукт такого уровня обязан иметь хороший годный тулинг для дебага.

Просто представьте. Вот у вас в системе один клиентский запрос, увы, обрабатывается 2-мя микросервисами последовательно (т.е. один дергает другой). Вот что-то упало во втором микросервисе. Не имея матчинга хотя-бы уровня correlation-id вы просто задолбетесь понимать из логов, что именно стало причиной падения. Даже 3-го уровня не нужно, чтобы появилась нужда в нормальном тулинге.

Так что я не думаю, что моя идея расходится с реальностью.

---

И раз уж мы говорим о солюшене типа netflix, то давайте говорить честно. Нужно сравнивать монолит размера netflix и сравнивать солюшен из микросервисов тоже размера netflix.

Если netflix внезапно переедет из микросервисов в монолит, то они не смогут выдерживать ни темпы разработки, ни требования к downtime; да и скейлится им будет тяжелее. Им будет тяжелее мерджиться, им будет тяжелее гонять тесты, им будет тяжелее деплоиться. Да и архитектура после такой миграции вообще поедет нафиг. Сэкономили на сетевых затратах, но зато получили пачку других проблем.

Netflix является и типичным, и атипичным примером. Типичным — потому что именно для таких компаний как netflix микросервисы дают огроменную выгоду. Атипичным — потому что в большинстве систем будет 10-100 микросервисов, а не 1000.

Чтобы выделить микросервис недостаточно логической целесообразности. Нужна ещё реальная независимость сервисов. В частности, есть правило, что микросервисы должны делиться так, чтобы не пересекать transaction boundary.

В идеале никакой сервис не должен дергать другой сервис. Если у вас после проектирования получилось так, что пайплайн запроса состоит из пяти микросервисов, то это серьёзная ошибка, имхо.

(т.е практически любая имплементация микросервисной архитектуры - неправильная, потому что мало у кого есть вообще необходимость в микросервисах, но все с ними хотят работать даже ценой боли)

Да, конечно, это оверкил и практически не нужно. Как и трюки с IBool и агрессивным инлайнингом.

В рабочем коде для первой итерации я бы оставил просто TODO

Да, на границах там ярый костыль, потому что сильно морочиться не хочется. И получилось квадратно-гнездовое.

Если по-хорошему. В случае, если у нас разрешены первые 3 месяца (1,2,3), а мы тыкаем в 4-й, то мы должны увеличить год и перенести на первый месяц. Т.е. должны выдать что-то типа (End + _allowedPoints.Min). Но это не будет работать для дней (потому что 32 и потому что разное количество дней в месяце) — а это уже черт знает как поправить.

Что это решение, что оригинальное — обладает одним существенным недостатком. Вместо того, чтобы быстро передвигаться к следующему числу в интервале, мы делаем инкремент и проверку, пока не найдем подходящее число. Это довольно тупо — мы же следующее число знаем.

Я решил сделать следующее. Взять SortedSet. С помощью SortedSet быстро находить следующее/предыдущее число в интервале. Потом еще отдельно проверил вариацию, что будет, если добавить мемоизацию. Итого, версия с чисто SortedSet в отдельных случаях дает ускорение x10; в большинстве случаев - наравне. Версия с SortedSet + мемоизация в отдельных случаях дает ускорение x20, в большинстве случаев - x2-x3.

Бенч FindNext
|                      Method       |              Pattern | DateString |        Mean |     Error |    StdDev | Ratio | RatioSD |
|---------------------------------- |--------------------- |----------- |------------:|----------:|----------:|------:|--------:|
|              FindNextNovar0       |          *.*.1 0:0:0 | 2001-01-01 | 17,993.6 ns | 175.25 ns | 155.35 ns |  1.00 |    0.00 |
|              FindNextPzixel       |          *.*.1 0:0:0 | 2001-01-01 | 18,085.6 ns | 101.72 ns |  84.94 ns |  1.01 |    0.01 |
| FindNextPzixelLovaSortedSet       |          *.*.1 0:0:0 | 2001-01-01 |  2,090.6 ns |  22.42 ns |  20.97 ns |  0.12 |    0.00 |
| FindNextPzixelLovaSortedSetCached |          *.*.1 0:0:0 | 2001-01-01 |  1,365.3 ns |  27.37 ns |  58.32 ns |  0.08 |    0.00 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       |          *.*.1 0:0:0 | 2080-05-05 |  2,594.6 ns |  50.99 ns |  60.70 ns |  1.00 |    0.00 |
|              FindNextPzixel       |          *.*.1 0:0:0 | 2080-05-05 |    978.1 ns |   5.79 ns |   5.42 ns |  0.37 |    0.01 |
| FindNextPzixelLovaSortedSet       |          *.*.1 0:0:0 | 2080-05-05 |    730.2 ns |  14.18 ns |  13.92 ns |  0.28 |    0.01 |
| FindNextPzixelLovaSortedSetCached |          *.*.1 0:0:0 | 2080-05-05 |    476.1 ns |   2.17 ns |   1.93 ns |  0.18 |    0.00 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       | *.4.6(...)-20/3 [31] | 2001-01-01 |    824.9 ns |  14.71 ns |  12.28 ns |  1.00 |    0.00 |
|              FindNextPzixel       | *.4.6(...)-20/3 [31] | 2001-01-01 |    489.4 ns |   7.31 ns |   6.48 ns |  0.59 |    0.01 |
| FindNextPzixelLovaSortedSet       | *.4.6(...)-20/3 [31] | 2001-01-01 |    940.0 ns |  13.61 ns |  13.36 ns |  1.14 |    0.02 |
| FindNextPzixelLovaSortedSetCached | *.4.6(...)-20/3 [31] | 2001-01-01 |    390.7 ns |   6.33 ns |   7.03 ns |  0.47 |    0.01 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       | *.4.6(...)-20/3 [31] | 2080-05-05 |  1,414.5 ns |  14.78 ns |  13.10 ns |  1.00 |    0.00 |
|              FindNextPzixel       | *.4.6(...)-20/3 [31] | 2080-05-05 |  1,112.4 ns |  22.10 ns |  27.95 ns |  0.79 |    0.02 |
| FindNextPzixelLovaSortedSet       | *.4.6(...)-20/3 [31] | 2080-05-05 |  1,313.8 ns |   6.49 ns |   5.75 ns |  0.93 |    0.01 |
| FindNextPzixelLovaSortedSetCached | *.4.6(...)-20/3 [31] | 2080-05-05 |    624.6 ns |   9.78 ns |   8.67 ns |  0.44 |    0.01 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       | *.9.*(...)0.000 [24] | 2001-01-01 |  1,898.9 ns |   9.64 ns |   8.55 ns |  1.00 |    0.00 |
|              FindNextPzixel       | *.9.*(...)0.000 [24] | 2001-01-01 |  1,249.0 ns |  23.94 ns |  21.22 ns |  0.66 |    0.01 |
| FindNextPzixelLovaSortedSet       | *.9.*(...)0.000 [24] | 2001-01-01 |    927.7 ns |   7.54 ns |   5.88 ns |  0.49 |    0.00 |
| FindNextPzixelLovaSortedSetCached | *.9.*(...)0.000 [24] | 2001-01-01 |    396.4 ns |   7.85 ns |  13.75 ns |  0.20 |    0.01 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       | *.9.*(...)0.000 [24] | 2080-05-05 |  1,661.2 ns |  22.89 ns |  21.41 ns |  1.00 |    0.00 |
|              FindNextPzixel       | *.9.*(...)0.000 [24] | 2080-05-05 |    999.3 ns |  19.71 ns |  18.44 ns |  0.60 |    0.02 |
| FindNextPzixelLovaSortedSet       | *.9.*(...)0.000 [24] | 2080-05-05 |    979.9 ns |  19.18 ns |  32.04 ns |  0.59 |    0.03 |
| FindNextPzixelLovaSortedSetCached | *.9.*(...)0.000 [24] | 2080-05-05 |    393.0 ns |   7.74 ns |  10.07 ns |  0.23 |    0.01 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       | 2100.(...)9.999 [23] | 2001-01-01 | 26,448.1 ns | 411.22 ns | 384.66 ns |  1.00 |    0.00 |
|              FindNextPzixel       | 2100.(...)9.999 [23] | 2001-01-01 | 25,061.1 ns | 485.91 ns | 631.82 ns |  0.95 |    0.03 |
| FindNextPzixelLovaSortedSet       | 2100.(...)9.999 [23] | 2001-01-01 |  1,910.0 ns |  37.01 ns |  34.62 ns |  0.07 |    0.00 |
| FindNextPzixelLovaSortedSetCached | 2100.(...)9.999 [23] | 2001-01-01 |    627.6 ns |   7.83 ns |   6.54 ns |  0.02 |    0.00 |
|                                   |                      |            |             |           |           |       |         |
|              FindNextNovar0       | 2100.(...)9.999 [23] | 2080-05-05 | 22,327.4 ns | 436.84 ns | 597.95 ns |  1.00 |    0.00 |
|              FindNextPzixel       | 2100.(...)9.999 [23] | 2080-05-05 | 20,506.1 ns | 403.14 ns | 431.36 ns |  0.93 |    0.03 |
| FindNextPzixelLovaSortedSet       | 2100.(...)9.999 [23] | 2080-05-05 |  1,957.0 ns |  34.26 ns |  30.37 ns |  0.09 |    0.00 |
| FindNextPzixelLovaSortedSetCached | 2100.(...)9.999 [23] | 2080-05-05 |    648.9 ns |  12.93 ns |  22.31 ns |  0.03 |    0.00 |

Модификациям в основном подверглись два файла, их можно глянуть здесь: PzixelLovaSchedule.cs и здесь ScheduleInterval.cs.

Подозреваю, что заменить мемоизацию на предпосчет, то вообще очень шустро будет.

Ну я упирал на токенизацию, потому что это один из способов повысить внятность кода. Да, вот сейчас @balabuev показал, что симпатично можно даже без токенизации.

Это switch expression. Не должен быть тотальным с точки зрения компилятора. Рантайм просто кинет исключение, если не найдёт подходящего кейса. См. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/switch-expression#non-exhaustive-switch-expressions

Хотя там написано, что в случае нетотальности получишь варнинг. И не очень понятно, как это проверяется, так что возможно ваш вопрос ещё актуален.

  1. Код действительно будет не очень большой, сравнимо по количеству строк — здесь довольно ясно.

  2. Результат важен хотя-бы по той простой причине, что при его формировании вы начнете создавать объекты. Ваша риторика про шизофрению серьезно посыпется. Она и так сыпется, если догадаться, что можно написать собственный Split, который будет возвращать структуры (например, ReadOnlySpan), а не объекты. Просто решение со Split будет top-bottom, а ваше решение — bottom-top. (Хотя split действительно не так устойчив к изменению формата)

  3. Особенность вашего кода — максимально полная императивность. С формированием результата это было бы забавно. Метод ParseListItem двигает нас по строке, и при этом возвращает данные. Это нарушает CQS и приводит к неустойчивому коду. А сейчас это не так заметно.

  4. Результат возвращать важно, потому что половина говнокода — это формирование результата. Туда же отправляется валидация ренджей. Или, например, логика, что если миллисекунды не указаны, то надо заполнить массивом [0], а если годы не указаны, то надо массивом [2000-2100]

  5. Я не очень понимаю, как вы узнаете, что код корректный. Ведь у вас не формируется результат, который можно проверить. Не самонадеянно ли?

Я вас может удивлю, но задачи на написание кода даются для того, чтобы проверить умение писать код, а не остроумие кандидата.

В случае миллисекунд их отсутствие значит не [0, 1, … 999], а [0].

В остальных случаях экономия будет несущественная, зато просядет производительность и читабельность (за счет дополнительного if).

Но все равно кода довольно много (45..177 — больше сотни строк).

Кода много, потому что используется только C#. Очевидно, что Pidgin как решение для парсинга будет предоставлять определенный сахар.

Кроме того, этот код является эквивалентом ParserHelper.cs 23-117 + ScheduleFormat.cs 21-71. Итого, у вашего решения 144 строки, у моего 132. Можно играться в подсчет строк и дальше, но вряд-ли найдется подсчет, показывающий существенную разницу.

И тут как раз минус в том, что нельзя собрать парсер из "кубиков" — нужно парсить сразу всё или ничего. Получилось выделить отдельно ParseCronSubRange — это конечно намного упростило результирующую регулярку. Но вот основной формат, увы, приходится матчить по принципу "пан или пропал".

Это ошибочное восприятие. Основной формат матчится целиком, потому что разбивать матчинг на блоки поменьше — это не очень осмысленно. Но мы, конечно же можем их разбить, просто получится чуть больше кода.

А вот в вашем случае вам просто необходимо разбивать на отдельные кубики, потому что если не разбить, то не получится пометить кубик как Optional.

Вопрос тут только в том, насколько легко можно подлезть туда, чтобы это можно было сделать.

Нужно реализовать свой IQueryable, который будет при создании query делать агрессивный инглайнинг Expression<Func<>> внутрь друг друга. В принципе, я не вижу такой уж сложности в задаче. Но работы предстоит дофига, конечно.

  1. Монадический парсер — очень клево.

  2. Вы вот повторяете, что решение на регулярках выглядело бы ужасным. См. мое: https://gist.github.com/vlova/544d693cc4083caafa477383b2e1c216. Неужели вам и вправду кажется, что такое решение ощутимо сложнее и read-only? (Но я согласен, что у регулярок есть много других недостатков в сравнении с полновесным парсером)

  3. К слову, о перфомансе парсера. Из того, что я прочитал о Pidgin, часть замедления идет от того, что он не полностью компилируется. В теории, если кто-то упрется в перфоманс Pidgin, то может быть резонно подправить его внутренности, а не переписывать код. В таком случае решение @novarне будет выигрывать вообще никак.

Information

Rating
Does not participate
Location
Харьков, Харьковская обл., Украина
Registered
Activity