Senior Solution Architect в EPAM
  • Що не так з загальними практиками REST API

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

    Accept-Encoding вирішує лише проблему ширини каналу. Для IoT пристрою вузьким місцем часто є не мережа, а CPU та RAM. Розпарсити стиснутий JSON, проаналізувати HATEOAS-посилання і вирішити, куди йти далі — це на порядок важча операція, ніж прочитати байти по фіксованому зміщенню в бінарному протоколі.

    Зміни протоколу бувають різні. HATEOAS вирішує деякі з них але не всі, та це все одне краще ніж нічого. Ще якісь проблеми стабільності вирішує підтримка версійності ресурсів. Для телеметрії є стандартні протоколи типу MQTT

    Фраза «краще ніж нічого» — це не інженерний аргумент. Щодо HATEOAS в телеметрії Ви так і не навели приклад, як саме клієнт має «адаптуватися». Якщо зміна протоколу вимагає перепрошивки пристрою, то HATEOAS там був лише зайвим вантажем.

    Згадка вами MQTT дуже доречна — вона якраз підтверджує мою тезу: для специфічних задач ми беремо специфічні інструменти, а не намагаємося всюди використовувати REST.

    І взагалі, коли у вас клієнту все одно необхідно мати якісь «домовленності» з сервером, будь-який формальний контракт буде кращим за його відсутність. Він дозволяє автоматизувати контроль зворотної сумісності, генерувати клієнти та сигналізувати розробникам клієнтів про deprecation частин контракту і т.д.

    Чи дозволено через PUT міняти статус?

    Ні, не дозволено. Зміна статусу є результатом дії а не її тригером.

    На вашому клієнті цей ресурс має неконсистентний стан, бо він «не побачив» зміни job.description, тільки зміни статусу, що він робив сам

    Оптимістичне блокування призначене для запобігання неконсистентності збереженого стану, і тут зміна опису і зупинка завдання один одному не заважають, тому не потребують блокування між собою. Те що ви кажете про клієнт не є проблемою — якщо хтось інший натисне «Стоп» через мілісекунду після того як завершилася зміна опису, я все одно не побачу зміну статусу, якщо не відбудеться ручного чи автоматичного оновлення поточного стану на клієнті.

    Так, наявність user id в локаторі ресурсу вирішить проблему з некоректним кешуванням.

    І створить проблему з розкриттям даних, про яку я кажу у статті: тепер знаючи ідентифікатори користувачів я можу ходити на відповідні URL, і якщо їх списки проєктів вже встигли закешуватися на якомусь вузлі, то я їх побачу без авторизації.

  • Що не так з загальними практиками REST API

    У ефекту Даннінга-Крюгера можуть бути різні прояви. Наприклад, коли досконало опановуєш складний молоток, весь світ починає здаватися цвяхами. З’являється ілюзія, що цим складним інструментом треба вирішувати абсолютно всі задачі, а будь-яке спрощення сприймається як некомпетентність.

    В інженерії зазвичай в кожної проблеми є декілька варіантів рішення, і в кожного рішення і відповідного інструменту є свої переваги і недоліки. Робота інженера полягає в тому, щоб обрати найбільш оптимальну комбінацію рішень, яка задовольняє вимоги при заданих вхідних умовах.

    Ви вказуєте на проблему еволюції контракту, яка існує на різних рівнях — в бібліотеках, базах даних, АПІ контрактах різної природи (не тільки HTTP), тощо. І для цієї проблеми давно існують сталі рішення і практики за межами REST.

    Ще складніше ситуація з оновленнями IoT клієнтів — часто це взагалі неможливо.

    Я не є експертом в області IoT, але, наскільки мені відомо, кінцеві пристрої використовують «легкі» бінарні протоколи, бо там з наявними ресурсами дуже важко і треба економити.

    Тим не менш, щоб остаточно зрозуміти, який саме сенс ви вкладаєте в HATEOAS — запропонуйте, будь ласка, API для взаємодії IoT сенсора з головною системою, через яку він має посилати дані телеметрії. І поясніть, яка має бути при цьому реалізація клієнта, щоб його не треба було оновлювати при зміні протоколу.

    Думаю відповідь очевидна.

    Неочевидна, є багато «але». Щоб не збивати фокус с більш принципових деталей, поки залишу без додаткових коментарів.

    Оскільки сутність `job` передбачає якийсь процес, подовжений у часі, то він повинен передбачати можливість достатнього моніторингу його стану ззовні, тобто можливість оцінити його поточний та минулий стан.

    Я не казав, що GET непотрібен, про три методи зі статті сказано «HTTP-ендпоїнти для керування виконанням вже створеного завдання виглядали б так». Ось вам повний набір методів як я його бачу:
    POST /api/v1/jobs — створити нове
    GET /api/v1/jobs/{id} — прочитати
    PUT /api/v1/jobs/{id} — оновити властивості (які можна оновлювати)
    POST /api/v1/jobs/{id}/start — почати виконання (в тому числі після призупинки)
    POST /api/v1/jobs/{id}/stop — зупинити
    POST /api/v1/jobs/{id}/pause — призупинити
    DELETE /api/v1/jobs/{id} — видалити

    Необхідність синхронізованої зміни стану, наприклад використовуючи оптимістичне блокування, вимагає якогось REST ресурсу, до якого ми могли б надіслати умовний запит, використовуючи заголовок `If: {ETAG}`.

    Поясніть, будь ласка, для чого нам потрібне оптимістичне блокування на методах зміни стану (start, stop, pause).

    еобхідність в `Vary:` заголовках зазвичай викликана неправильним дизайном REST ресурсів, через наявність ресурсів типу `/my-profile` замість використання канонічних ресурсів `/profiles/{id}`

    Типова ситуація: є система де користувач має бачити список проєктів компанії, але через різний рівень пермісій кожен користувач бачить різний список проєктів. Чи правильно я вас розумію, що замість /projects мені треба зробити щось на кшталт /user/{id}/projects?

  • Що не так з загальними практиками REST API

    Мова про те, що HATEOAS, який є невід’ємною частиною архітектурного стилю REST, по факту використовується в дуже обмеженій кількості випадків. В його парадигмі клієнти не мають ніяких знань про схему доменної моделі наперед. Для більшості систем, які ми всі робимо, таке обмеження значно ускладнює реалізацію не надаючи помітних переваг. А тому і не бачу сенсу суворо дотримуватися практик, які були сформульовані саме для підтримки такої парадигми.

    У висновках до статті я нещодавно додав, якого підходу в іменуванні ендпоїнтів я завжди дотримувався, щоб було зрозуміліше чи пропагандую я щось чи ні. Ніякої революції тут немає — просто констатація того, що вже давно є поширеною практикою.

  • Що не так з загальними практиками REST API

    GET — повністю актуальний бо навіть в прикладі що я навів саме так вони це і роблять, ви без цього ніяк. Щоб керувати станом вам майже 100% треба буде знати в якому зараз стані задача
    DELETE — чому ні якщо треба відмінити задачу
    POST — можливо не треба якщо ми не створюємо саму задачу, але не рідко його використовують як для створення так і для апдейта просто в залежності чи надається ІД чи ні виконуються різні дії.

    Коли я підкреслив «для керування виконанням вже створеного завдання», я мав на увазі тільки методи які змінюють стан виконання.
    DELETE в цьому прикладі не підходить для зупинки задачі, тому що зупинка і видалення — це різні операції. Після зупинки я хочу залишити задачу в історії, щоб можна було подивитися дані по завданню і якщо що — запустити нове з такими самими вхідними даними. Те на що вказуєте ви — це специфічний випадок, де зупинка/відміна еквівалентна видаленню.

    Якщо що, я б робив повний набір методів для свого прикладу таким:

    POST /api/v1/jobs — створити нове
    GET /api/v1/jobs/{id} — прочитати
    PUT /api/v1/jobs/{id} — оновити властивості (які можна оновлювати)
    POST /api/v1/jobs/{id}/start — почати виконання (в тому числі після призупинки)
    POST /api/v1/jobs/{id}/stop — зупинити
    POST /api/v1/jobs/{id}/pause — призупинити
    DELETE /api/v1/jobs/{id} — видалити

    Питання на якому я фокусуюся в прикладі зі статті — чи має сенс ці три окремих методи (start/stop/pause) ховати за PUT/PATCH чи ні.

    Те що далі може бути прорва роботи то інша тема, я лише про ту частину яка формує зовнішній інтерфейс.

    Про те і мова, що зовнішній інтерфейс має бути якомога зрозумілішим і легким в розширенні.

    Але ж в черзі у вас задачі будуть мати цілком конретні статуси щоб ваш код який їх виконує завжди знав на чому він зупинився і яка частина системи повинна далі з ним працювати.

    Будуть мати статус, і це буде read-only властивість. Ми не просто встановлюємо статус «Running», ми наказуємо системі «Запустити процес». Згідно загальній логіці, статус «Running» — це лише наслідок успішного виконання цієї команди, а не вхідний параметр.

    Формально я не можу сказати чи один підхід кращий за інший, але якщо ми додаємо якісь дії в path частину url це певною мірою розходиться з тим до чого люди звикли.

    Про це і стаття, що «люди звикли», хоча на практиці ця звичка часто не несе практичної користі.

    Та все що завгодно можна додати в body.

    Коли ви робите PUT на якийсь ресурс, вказуючи перелік властивостей, то скоріш за все маєте на увазі, що у цього ресурсу такі властивості є, а не просто що у дії «оновити статус» є додаткові параметри. В найпростішому випадку можна було б сказати — «оскільки завдання можна зупинити один раз, то нормально мати властивість з причиною зупинки», і тоді ваш приклад був би валідним. Але, наприклад, завдання можна поставити на паузу багато разів, і бажано зберігати історію причин кожної зупинки. В цьому випадку ваш приклад не дуже підійде — бо у ресурсу є історія, в якій буде декілька переходів стану, і деякі з них — з причиною від користувача. Тобто властивості «причина» на самому ресурсі завдання не буде, а додавати її штучно в пейлоад для PUT — ще сильніше заплутує інтерфейс.

    Є дії які можна зробити з сутністю, а є властивості. Для зміни властивостей нормально мати загальний метод на всіх, але для окремих дій краще виділяти окремі методи, бо у них, окрім всього іншого, можуть бути параметри і з плином часу їх список може розширюватися. Заміна явної дії оновленням властивості зменшує гнучкість і прозорість АПІ.

  • Що не так з загальними практиками REST API

    В натуральних мовах ми передаємо інформацію присудком (delete) + підметом (job) + «аргументами» (id=123).
    В мовах програмування зводиться до того ж самого: чи то виклик методу deleteJob(id=123), чи то відправка меседжа об’єкту job.
    В remote API задача та ж сама. REST-ish чи не RPC-ish — вже деталь навіть не реалізації, а «серіалізації» цих викликів.

    Саме на це я і вказую у статті. Нюанс в тому, що іноді для слідування класичним рекомендаціям для RESTful API, деякі дії ховають за зміною властивостей об’єкту, що робить семантику взаємодії з таким сервісом менш зрозумілою.

    Чи хочу я писати всраті OpenAPI-специфікації, якщо можу десь використати gRPC натомість? Майже ніколи.

    Повністю згоден, стаття сфокусована саме на «класичних» HTTP API, тому gRPC та інші аналоги я не згадував. Щодо OpenAPI — досить часто достатньо генерити специфікації кодом бекенду автоматично, якщо серверний фреймворк таке дозволяє. Отримуєте і плейграунд, і можливість згенерити клієнти по специфікації не витрачаючи час на написання специфікації вручну. Трохи пізніше додам в статтю більш розгорнуту думку про OpenAPI, бо схоже тут мене багато хто неправильно зрозумів.

    Чи візьму я REST-ish + OpenAPI замість того, щоб придумувати власний RPC-протокол? Майже завжди.
    Чи є мені різниця, як виглядає протокол, якщо мені дають класно зроблений SDK під мою мову? Ні. Аби тільки продебажити можна було у разі проблем.

    І тут я повністю згоден, і стаття цьому не протирічить :) Якщо вкажете які саме формулювання призвели до непорозуміння — буду вдячним.

  • Що не так з загальними практиками REST API

    Так саме про це і стаття, що витрачати зайві зусілля на спроби зробити «все по ресту» не має практичних переваг у більшості випадків.

  • Що не так з загальними практиками REST API

    Так я і не кажу про якісь з вказаних практик, що вони погані. Проблема саме в тому, що для розробки традиційних HTTP API, які ніколи не будуть використані в рішенні на базі HATEOAS, все одно витрачають час на «слідування традиціям». Щодо «складних систем» — це занадто неконкретне узагальнення. Я бачив щось схоже на HATEOAS в рішеннях для Data Governance, де можна навігувати графами доменних об’єктів і редагувати їх, але це дуже специфічний випадок. Також, з деякими нюансами, можу уявити як це спрацює в контент-порталах. Можна ще уявити сценарії, де кінцевий користувач взаємодіє з контентом через узагальнений UI, але в будь-якому випадку — це не мейн-стрим, тому не бачу в чому моє твердження «не має жодних переваг у більшості сценаріїв» некоректне.

  • Що не так з загальними практиками REST API

    Я не просто так ці «канони» взяв в лапки, але, тим не менш, у терміна є першоджерело. В ньому є набір архітектурних обмежень, але про прив’язку до HTTP нічого не говориться, тому що сам концепт високорівневий і protocol-agnostic. Відповідно, твердження

    REST — це набір практик для HTTP API, не більше.

    фактично некоректне.

    А ось з цим твердженням погоджусь:

    Те що ми розробляємо це не обов’зяквово API що реалізує паттерни REST, це HTTP API, просто ютубери, блоггери та інфобізнесмени вигадали поняття RESTfull, і просували його, що було не вірно.

    І для розробки зрозумілого HTTP API яке легко використовувати і підтримувати, зовсім необов’язково слідувати усім «заповідям» які асоціюють з терміном RESTful, про що власне і стаття.

  • Що не так з загальними практиками REST API

    Я переписав речення у статті на «Найочевидніші HTTP-ендпоїнти для керування виконанням вже створеного завдання виглядали б так», щоб акцент був зрозумілішим. Ваші приклади POST/GET/DELETE тут нерелевантні, бо мова йде про керування виконанням.

    Щодо

    PUT /api/v1/jobs/{id} + {body} return {job + id} (оновлюємо задачу можливо переведеням на паузу)

    Я саме про недоліки такого варіанту нижче в статті і пояснюю. В даному випадку зміна стану — це фізично не просто оновлення поля, а цілий набір дій з «виконавцем» — чи то системний процес, джоба в k8s, чи ще щось.

    З точки зору клієнта який є чи користувачем, що *натискає кнопку* «старт» або «пауза», чи автоматизацією, якій треба виконати *дію*, це не оновлення властивості, а конкретна дія — «почати виконання», «призупинити виконання», тощо.

    Тобто в вас і у клієнта це дії, і на сервері — окремі функції під кожен перехід стану. Нащо між ними додавати штучну абстракцію через оновлення властивості? Що це дає практично?

    Інший аспект — наскільки очевидним буде такий АПІ для девелопера або для якого-небудь АІ агента. Коли читаєш окремі методи під типові дії — все видно відразу, а коли цілий набір доступних дій прихований під зміною властивостей — можуть виникати питання.

    І ще одне — а що якщо нам знадобляться параметри у таких операцій — наприклад, причина зупинки чи паузи?

  • Що не так з загальними практиками REST API

    Як я розумію, первинна ідея HATEOAS полягала не в тому, щоб клієнт міг «інтуїтивно досліджувати» незнайомий API. Сенс був у тому, щоб клієнт працював із символічними лінками, які повертає бекенд, і саме назви цих лінків є частиною контракту. Натомість реальні URL-адреси за цими лінками не повинні бути для клієнта значущими — бекенд має право змінювати їх без необхідності оновлювати клієнт.

    Якщо ви про «Хтось може поспорити, що HATEOAS усе ще актуальний, адже його динамічна структура, яка дозволяє клієнту самостійно орієнтуватися в API без знання специфікації, добре підходить для сучасних AI-агентів» — то це тільки приклад думки, яку я зустрічав як виправдання HATEOAS. Основні мої власні міркування про HATEOAS я написав в розділі про ресурси і вони не протирічать написаному вами.

    Щодо використання пост-запитів з командами (POST на ендпоінт-команду, що описується дієсловом), то це також є частиною загальноприйнятої практики, що описується в багатьох керівництвах, наприклад: https://dotnet.rest/docs/bestpractises/post-vs-put/

    Воно все так, але формально цей підхід протирічить «канонам REST», адже ендпоїнт стає не ресурс-орієнтованим, і тому не всі на нього погоджуються з релігійних міркувань. Саме тому я в статті хотів підкреслити, що це цілком нормальний варіант.

  • Що не так з загальними практиками REST API

    Щодо ресурсно-орієнтованості, то її основна перевага — зрозумілість для команд клієнтів, коли сервер не надає «клієнтську бібліотеку». Але це також залежить від того наскільки ви прив’язуєте ваше АПІ до РЕСТ як архітектурного стилю і як результат варіативність АПІ, яку ви хочете мати. Наприклад, чи хочете ви мати можливість віддавати дані у різних форматах (ХМЛ на додачу до ДжСОН).

    Я не розумію що буде незрозумілого для команд клієнтів, якщо сервер буде виставляти ендпоїнти в RPC стилі, а не в ресурсному. Формати — взагалі ортогональне питання, можна керувати параметрами та заголовками незалежно від того ресурсно-орієнтована семантика чи RPC.

    Одна крайність — це пуризм (в нашому випадку щодо РЕСТ), але інша — це відкидання всіх практик, як непотрібних, мотивуючи істуванням альтернативного підходу.

    Я у висновках як раз і пишу «Ідея, що всі ендпоїнти повинні бути ресурсно-орієнтованими, не має жодних переваг у більшості сценаріїв». Тобто я не кажу що ресурсна орієнтованість це в цілому погана практика, а наголошую на тому, що якщо зручніше щось зробити без неї — go for it.

    Але нижче Nikita Podgorbunskyi запропонував ресурсно-орієнтоване АПІ, яке мені дещо зрозуміліше ніж запропоноване вами. Я не бачу об’єктивних/вимірюваних критеріїв чому треба обрати те чи інше.

    Тут я зрозумів що можливо недостатньо чітко окреслив скоуп прикладу. Я навів приклади ендпоїнтів тільки для стану виконання завдання (запуск, зупинка, пауза), а не всього життєвого циклу самого об’єкту (створити, прочитати, видалити). Завтра напишу детальніше, може статтю трохи оновлю.

    А тепер хвилинка крику і болю: OpenAPI — гівно!!!

    У нього є недоліки, але для мейнстрім АПІ зрілих поширених альтернатив небагато :-)

    — Клієнт — це компайл тайм залежність. Клієнтська команда не може почати розробку поки не отримає бібліотеку.

    Можна робити specification-first і генерувати бібліотеку для клієнта і стаби для сервера зі специфікації.

    — Комбінації схем (oneOf, anyOf, allOf, not). Воно не працює нормально навіть в мовах де підтримуються такі типи, а в джаваподібних то взагалі пекло.

    Є таке, тому іноді набір використаних конструкцій з цієї специфікації має сенс обмежувати.

  • Як перестати перекладати в голові та справді почати говорити англійською

    Якщо не знаурюватися в різноманітні техніки вивчення нових слів та закріплення мовних конструкцій, я би додав ще пару порад:

    • На етапі коли говорити ще важко і немає змоги повсякденно спілкуватися цією мовою з живими людьми, непогано допомагає написання текстів на будь-які теми з подальшою перевіркою через LLM (причому так, щоб LLM пояснював помилки). Не так стрессово, як говорити, бо є час підібрати слова, але добре тренує вміння формулювати думки іноземною мовою. Важливо саме не перекладати вже готовий текст, а намагатися формувати його відразу «цільовою» мовою, підглядяючи в словник і перекладач тільки коли зовсім важко.
    • Для прокачки розуміння англійської на слух треба поступово підвищувати складність контенту. Бо якщо відразу взяти кіно та стендапи, можна швидко зневіритися. Я би розташував складність за зростанням таким чином (список не вичерпний):
      • навчальні аудіо, де говорять чітко і повільно
      • новини
      • пізнавальні Ютуб роліки
      • інтерв’ю
      • кіно та серіали
      • стендап
      • пісні
    Щодо правильної вимови — бачу, що не всі, нажаль, розуміють що мається на увазі. Це не про те що «обов’язково треба звучати як нейтив». Правильна вимова — це дві речі:
    1. Правильна вимова слів, тобто відтворення правильної послідовності фонем з правильним наголосом. Наприклад, не «енджайн», а «енджин». І для визначення «правильності» треба буде обрати ваш цільовий варіант англійської, бо незалежно від кількості регіональних акцентів, в кожному з них є загальний стандарт — американська, британська та інші. Вимова якого-небудь advertisment може відрізнятися між ними. До речі, про особливо «одіозні» слова зі сфери IT я писав статтю — dou.ua/forums/topic/39356.
    2. Правильна артикуляція звуків, тобто використання правильної механіки їх відтворення навіть якщо це не звучить «точно як у нейтивів». Невміння вимовити звуки для th або невміння відтворити різницю між «i» та «ee» не сильно допоможуть іншим вас краще розуміти. І знову таки, незалежно від кількості регіональних акцентів, є стандарти. Має сенс обрати один з них як орієнтир (наприклад, General American або Received Pronunciation) і триматися його послідовно, щоб не змішувати різні фонетичні системи.
  • Чому більшість «перевикористаного» React-коду перетворюється на over-engineering і як правильна архітектура вирішує цю проблему

    Нарешті хоч хтось про це написав, дякую :). Все ніяк не знайду час написати статтю «Що не так з Реакт», в якій хотів пройтися по цих сталих практиках, що генерять складність на рівному місці. Ну і також по Redux за оверінжиніринг заради оверінжиніринга. На щастя, зараз вже у фронтовий код нечасто доводиться дивитися :).

    Підтримали: Mykola, Mykhailo Rozdorozhniuk
  • Розв’язуємо задачі з LeetCode. Три приклади на C#

    Раджу ознайомитися з теорією парсингу виразів, принаймні базові речі типу рекурсивного спуску (recursive descent parser). На практиці ваше рішення для задачі з парсингом було б важко підтримувати і розширяти новими можливостями виразів. Ось як би я розв’язував цю задачу (тут можуть бути неточності, бо накидував швидко, але основна ідея така)

    public class ExpressionEvaluator
    {
        public static bool Evaluate(string expression) => Evaluate(new StringReader(expression));
    
        public static bool Evaluate(TextReader reader) =>
            reader.Read() switch
            {
                -1 => throw new ArgumentException("Unexpected end of string"),
                't' => true,
                'f' => false,
                '!' => EvaluateUnary(reader, a => !a),
                '&' => EvaluateBinary(reader, (a, b) => a && b),
                '|' => EvaluateBinary(reader, (a, b) => a || b),
                var ch => throw new ArgumentException($"Unknown token \"{(char)ch}\"")
            };
    
        private static bool EvaluateUnary(TextReader reader, Func<bool, bool> predicate)
        {
            EnsureToken(reader.Read(), '(', "Missing opening bracket");
            var result = predicate(Evaluate(reader));
            EnsureToken(reader.Read(), ')', "Missing closing bracket");
    
            return result;
        }
    
        private static bool EvaluateBinary(TextReader reader, Func<bool, bool, bool> predicate)
        {
            EnsureToken(reader.Read(), '(', "Missing opening bracket");
    
            List<bool> values = [];
    
            int token;
            do
            {
                values.Add(Evaluate(reader));
                token = reader.Read();
            }
            while (token == ',');
    
            EnsureToken(token, ')', "Missing closing bracket");
    
            if (values.Count < 2)
            {
                throw new ArgumentException("Not enough operands for a binary operator");
            }
    
            return values.Aggregate(predicate);
        }
    
        private static void EnsureToken(int token, char expectedToken, string errorMessage)
        {
            if (token == -1)
            {
                throw new ArgumentException("Unexpected end of string");
            }
            else if (token != expectedToken)
            {
                throw new ArgumentException(errorMessage);
            }
        }
    }
    Підтримав: Serhii Voichyk
  • Код, який боляче читати: як ми провели конкурс на найкращий спагеті-код

    Є підозра що воно генерує нову перестановку бітів з числа що на вході. Якщо не помиляюся, кількість одиниць на вході і на виході виходить однаковою.

    Підтримали: Andrii Sevastianov, Dima Pylypenko
  • LINQ у .NET: технічна реалізація базових методів і суміжні концепції

    Почуйте мене, будь ласка. Володимир каже, що термін LINQ — це тільки про query syntax. Я кажу — що ні, не тільки. І цитати яки ви наводите з документації саме це і підтверджують. Тут суто термінологічне питання. Про що ми споримо?

    Підтримав: Vladyslav Burylov
  • LINQ у .NET: технічна реалізація базових методів і суміжні концепції

    Тобто ви підтверджуєте думку Володимира що LINQ — то тільки SQL-подібні експрешени? А екстеншен-методи Where і Select з бібліотеки System.Linq — то не LINQ? Бо його ствердження

    Linq то трохи не про те

    я розумію саме так. Чи костиль воно чи ні — то вже інше питання.

  • LINQ у .NET: технічна реалізація базових методів і суміжні концепції

    Мій комент був про цю частину ствердження Володимира:

    Linq то трохи не про те. Linq то отой sql-подібний костиль,
  • LINQ у .NET: технічна реалізація базових методів і суміжні концепції

    Тому що в LINQ можна використовувати два вида синтаксису — або Query Syntax, або Method Syntax, і незалежно від обраного вами типа синтаксису це все одно буде LINQ. Бо LINQ — це не тільки Query Syntax, а цілий набір засобів в .NET. Можна ще тут більш загальне формулювання почитати — learn.microsoft.com/...​en-us/dotnet/csharp/linq

  • Підводні камені асинхронного обміну повідомленнями

    но через симуляцію обміну мессаджами назвавши це «іншої реалізації по різному» )) профітЪ

    Цей комент каже про те, що ти прирівнюєш поняття «асинхронна взаємодія» і «асинхронний обмін повідомленнями». І схоже весь твій потік демагогії, пересмикувань і намагань «підколоти» викликаний саме цим базовим непорозумінням. Просто уточню: стаття про обмін повідомленнями, а не про асинхронну взаємодію в цілому. Тут я можу тільки порадити Google та ChatGPT для з’ясування відмінностей, можеш ще англійською порівняти asynchronous communication та asynchronous messaging, щоб виключити «складнощі перекладу».

    Це якби я написав статтю про плюси і мінуси вантажівок, де б зазначив, що тільки для того щоб возити дитину до школи вантажівка непотрібна, а ти б відповідав що я дебіл, бо вантажівка це теж машина, а возити дітей до школи на машині — це норм.

    Нажаль, вести конструктивну дискусію ти чи не хочеш чи не вмієш. Робити припущення щодо причин не бачу сенсу, як і продовжувати розмову. Але удачі тобі в пошуках самоутвердження!

← Сtrl 1234567 Ctrl →