{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiawwaxpnfucnqxdodzpaxuv6lrmiwz7gdxguxmr24bb4aq7it234q",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpq3bnxkfex2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreicmhfah7pgeydm3qcnwacieimzsiqrsvxoelk4nynxreod43zycku"
},
"mimeType": "image/webp",
"size": 431954
},
"path": "/adamanq/ash-sposoby-vzaimodieistviia-ashjsonapi-ashgraphql-i-ashtypescript-1h9p",
"publishedAt": "2026-07-03T07:50:45.000Z",
"site": "https://dev.to",
"tags": [
"elixir",
"ash",
"phoenix",
"backend",
"Ash Framework: Знакомство",
"AshJsonApi",
"jsonapi.org",
"AshGraphQL",
"официальное руководство по мониторингу Ash",
"AshTypescript",
"телеграм-канале"
],
"textContent": "Разработка стабильного продукта, способного держать высокие нагрузки на разных серверах, — задача сложная и ресурсозатратная. Программист не может сфокусироваться только на бизнес-логике: сначала нужно настроить базу данных, спроектировать серверные **_API_** и многое другое.\n\nСложность многократно возрастает при работе в одиночку. Приходится самому реализовывать интерфейс на выбранном фреймворке, обеспечивать совместимость слоёв, собирать проект в образы **_Docker_** и разворачивать его в **_Kubernetes_**. Добавьте сюда настройку маршрутизации через **_Nginx_** и проектирование системы по принципам чистой архитектуры — и получится крайне трудоёмкий процесс.\n\nПоэтому во многих языках активно внедряют инструменты для автоматизации рутины (_бойлерплейта_), позволяя сосредоточиться на уникальном коде. Например, в **_Go_** есть библиотека **_oapi-codegen_** : вы описываете контракт в формате **_Swagger_** , а по нему автоматически генерируется код сервера. Такой формат упрощает интеграцию фронтенда и бэкенда и делает изменения логики менее болезненными — достаточно обновить описание в **_Swagger_** -файле и перегенерировать сервис.\n\nПодобные подходы к систематизации повторяющихся задач появляются повсеместно. Главная цель — сделать интеграцию предсказуемой и исключить ситуацию, когда разработчику приходится десятки раз переписывать и тестировать одно и то же ради идеальной совместимости. И в этой сфере есть свой лидер — **Ash**!\n\nㅤ\n\n\nㅤ\n\n\n## 🙏 Благодарности\n\nХочу выразить глубокую признательность Заку Даниэлю и всем, кто отреагировал на мою предыдущую статью об **_Ash_** (Ash Framework: Знакомство) и поделился ею. Ваша поддержка дала огромный заряд энергии и мотивации и подтвердила, что сообщество **_Ash_** — потрясающее!\n\nㅤ\n\n\n## 📖 Предисловие\n\nВ этом материале намеренно не будет практическое руководство в полном объёме по указанным темам, так как полагаемся на вашу любознательность. Несмотря на желание охватить всё, я не смогу в одной статье описать всю практическую сторону данных фреймворков и научить вас всему и сразу. Вместо этого я поделюсь с вами информацией и задам направление для размышлений, что и является основной целью данной публикации. Для более глубокого погружения в работу этих библиотек я рекомендую вам начать создавать на их основе собственные проекты — именно такой подход когда-то помог мне лучше всего разобраться в их устройстве. Помните, что главным наставником для вас станет практика, поэтому успехов в её освоении!\n\nВажно: цель статьи — вовсе не уговорить вас использовать Ash повсеместно и постоянно!\n\nㅤ\n\n\nㅤ\n\n\n## 🧐 Что предлагает экосистема Ash\n\nЕсли вы знакомы с **_Elixir_** и **_Phoenix_** , то знаете, что фреймворк уже содержит встроенные механизмы для упрощения разработки. Один из них — **_LiveView_**. Он позволяет отказаться от классического разделения на **_API_** -бэкенд и отдельный **_JavaScript_** -фронтенд: интерфейс пишется прямо внутри приложения. Это ускоряет разработку и удобно бэкенд-программистам, работающим без фронтенд-команды. Для простых и средних проектов путь с **_LiveView_** отлично подходит, но при создании масштабных систем код рискует превратиться в «спагетти», где трудно отслеживать зависимости между компонентами и находить обработчики вроде `def handle_params/3`.\n\nА как быть, если нужен качественный обособленный фронтенд, но вы устали каждый раз заново писать шаблонный **_REST_** -интерфейс и настраивать связь? Ответ — освоить возможности **_Ash_**. О них и поговорим.\n\nㅤ\n\n\nㅤ\n\n\n## 😮 REST с AshJsonApi: новый подход\n\nСтандартная практика подразумевает фиксацию контракта между фронтендом и бэкендом не только на словах, но и документально — обычно через **_Swagger_**. В нём описывают структуры данных и методы API, после чего документация передаётся команде интерфейса. Любые изменения требуют осторожности, чтобы не сломать совместимость. Даже базовая настройка такого подхода стоит немалых усилий: нужно интегрировать библиотеку генерации, создать конфигурации по спецификации OpenAPI (_Swagger_) и вручную описать конечные точки (_endpoints_). Если речь о переписывании существующего проекта, процесс легко занимает больше рабочего дня.\n\nAshJsonApi предлагает альтернативу. Главное преимущество — вам больше не нужно предварительно писать спецификацию в **_YAML/JSON_** и генерировать из неё код. Для старта достаточно готового домена ресурсов и подключённого расширения **_AshJsonApi_**.\n\nСоздать новый проект с поддержкой этого инструмента можно прямо на главной странице портала **_Ash_** через конструктор. Если проект на **_Ash_** уже есть, добавить функциональность можно одной командой:\n\n\n\n mix igniter.install ash_json_api\n\n\nВ процессе установки система задаёт уточняющие вопросы (_Y/N_). Можно соглашаться со всеми предложениями или изучить каждый пункт и решить самостоятельно. После установки останется выполнить несколько простых шагов.\n\n### Настройка ресурса для работы с JSON:API\n\nЧтобы ресурсы стали доступны через **_API_** , подключите расширение **_AshJsonApi.Resource_**. Это позволит автоматически сгенерировать эндпоинты на основе определений домена. Добавьте `AshJsonApi.Resource` в список `extensions` и задайте имя ресурса в параметре `type`:\n\n\n\n defmodule CourseHub.Courses.Lesson do\n use Ash.Resource, extensions: [AshJsonApi.Resource]\n\n # Активация генерации эндпоинтов\n json_api do\n type \"lesson\"\n end\n\n actions do\n defaults [:read, :create, :destroy, :update]\n\n action :list_lessons_allowed, :map do\n run fn _input, _ctx ->\n allowed_courses = %{\"go\" => \"Golang\", \"elixir\" => \"Elixir\", \"rust\" => \"Rust\"}\n {:ok, allowed_courses}\n end\n end\n end\n end\n\n\nПараметр `type` определяет имя ресурса в пространстве имён JSON:API. Он используется преимущественно для документации (_например, в**Swagger** или **Redoc**_) и позволяет группировать методы по тегам.\n\nДалее опишите маршруты в домене, указав пути и связав их с конкретными действиями ресурса:\n\n\n\n defmodule CourseHub.Courses do\n use Ash.Domain, extensions: [AshJsonApi.Domain]\n\n json_api do\n routes do\n base_route \"/lessons\", CourseHub.Courses.Lesson do\n index :read, description: \"Получение списка лекций\"\n get :read, primary?: true, description: \"Получение одной лекции\"\n post :create, description: \"Создание лекции\"\n patch :update, description: \"Изменение лекции\"\n delete :destroy, description: \"Удаление лекции\"\n\n # Путь относителен base_route, итоговый эндпоинт: /lessons/allowed\n route :get, \"/allowed\", :list_lessons_allowed,\n description: \"Получение списка разрешённых лекций\"\n end\n end\n end\n end\n\n\nВыполнив эти шаги, вы получите шесть полностью функциональных эндпоинтов. Вся логика теперь связана с вашими **_actions_** внутри доменной модели. Отдельные **_Swagger_** -файлы или ручное определение маршрутов не нужны: изменение логики action в коде автоматически отражается на поведении соответствующего **_API_** -эндпоинта.\n\n### Ключевые достоинства AshJsonApi\n\n#### Единый подход и предсказуемость\n\nОсновное преимущество — строгая согласованность приложения. При переходе на другой тип контракта (_например,**GraphQL**_) не придётся переписывать бизнес-логику. Поскольку **_AshJsonApi_** работает поверх ваших **_actions_** , вы застрахованы от ситуации, когда изменения в коде не отразились в одном из интерфейсов: вся логика инкапсулирована в **_action_** , который вы «раздаёте» разным контрактам. Это заметно ускоряет разработку — по личным оценкам, готовый продукт получается примерно в пять раз быстрее. Для автоматизации можно воспользоваться командой:\n\n\n\n mix ash.patch.extend CourseHub.Courses.Lesson json_api\n\n\nУказав адрес ресурса, вы позволите **_AshJsonApi_** автоматически сгенерировать всю обвязку для работы с **_API_**.\n\n#### Работа с данными: фильтрация, сортировка и связи\n\nКак бэкенд-разработчик, я раньше тратил много времени на рутинную реализацию фильтров и сортировок, вручную составляя **_SQL_** с **_LIMIT_** , **_OFFSET_** и **_WHERE_**. **_AshJsonApi_** избавляет от этого: фильтрация и упорядочивание выполняются автоматически. Фронтенду достаточно указать нужные поля и условия прямо в запросе. Это кардинально упрощает взаимодействие команд: фронтенд формирует запросы к данным самостоятельно, не отвлекая бэкенд. При этом все вычисления происходят на сервере, поэтому клиент не несёт лишней нагрузки — он лишь описывает желаемую схему данных. Полный список параметров есть в официальной документации. Пример запроса:\n\n\n\n GET /courses/1?include=lessons&included_page[lessons][limit]=10&included_page[lessons][offset]=20\n\n\nЗапрос возвращает информацию о курсе и подгружает связанные лекции с заданной пагинацией. Ответ приходит в формате **_application/vnd.api+json_** — это стандартизированная спецификация (подробнее на jsonapi.org), которая структурирует JSON для предсказуемой работы.\n\nПример ответа:\n\n\n\n {\n \"data\": {\n \"id\": \"1\",\n \"type\": \"course\",\n \"attributes\": {\n \"title\": \"My Course!\"\n },\n \"relationships\": {\n \"lessons\": {\n \"data\": [\n {\"id\": \"1\", \"type\": \"lessons\"},\n {\"id\": \"2\", \"type\": \"lessons\"}\n ],\n \"links\": {\n \"self\": \"/courses/1/relationships/lessons\",\n \"related\": \"/courses/1/lessons\",\n \"first\": \"/courses/1?include=lessons&included_page[lessons][limit]=10\",\n \"next\": \"/courses/1?include=lessons&included_page[lessons][limit]=10&included_page[lessons][offset]=10\",\n \"prev\": null,\n \"last\": \"/courses/1?include=lessons&included_page[lessons][limit]=10&included_page[lessons][offset]=40\"\n },\n \"meta\": {\n \"limit\": 10,\n \"offset\": 0,\n \"count\": 50\n }\n }\n }\n },\n \"included\": [\n {\n \"id\": \"1\",\n \"type\": \"lessons\",\n \"attributes\": {\n \"description\": \"Lessons about Elixir 1!\"\n }\n },\n {\n \"id\": \"2\",\n \"type\": \"lessons\",\n \"attributes\": {\n \"description\": \"Lessons about Elixir 2!\"\n }\n }\n ]\n }\n\n\n#### Заключение и личный опыт\n\nПодводя итог, поделюсь опытом. На текущем проекте создание **_REST_** -эндпоинтов заняло всего 20–30 минут. Основное время ушло на кастомизацию отображения документации в **_Redoc_** — вот это оказалось довольно трудоёмким (_в отличие от генерации самого**API**_). Если решите использовать **_Redoc_** , будьте готовы вручную переписывать его блоки ради красивого вывода ответов и примеров.\n\nВ остальном инструмент показал себя отлично: великолепная гибкость, кастомизируемая обработка ошибок, тонкое управление доступом к эндпоинтам. Если цель — построить удобный и мощный **_REST API_** , **_AshJsonApi_** отличный выбор.\n\nㅤ\n\n\nㅤ\n\n\n## 🙂↕️ AshGraphQL\n\nС этой библиотекой возможны сложности: не так много людей вообще работали с **_GraphQL_**. Поэтому сначала дам вводную — освежить память или узнать, что это такое, — а потом посмотрим саму библиотеку.\n\n### Что такое GraphQL\n\n**_GraphQL_** — это язык запросов для **_API_** и рантайм для их исполнения. Клиент сам описывает, какие данные ему нужны, а сервер возвращает ровно эту форму. Контрактом служит строго типизированная схема.\n\n#### Фундаментальные принципы\n\nВзаимодействие идёт через единый **_endpoint_** (_обычно`POST /graphql`_). Логика строится вокруг графа типов. Запрос имеет иерархическую структуру, и ответ сервера в точности её повторяет. Это решает проблемы **_over-fetching_** и **_under-fetching_**.\n\n#### Виды операций\n\n * **Query** — операции на чтение (идемпотентны).\n * **Mutation** — операции на изменение данных (выполняются последовательно).\n * **Subscription** — подписка на поток событий в реальном времени (через _WebSocket_).\n\n\n\n#### Схема и резолверы\n\nЦентральный элемент — схема, описываемая на **_SDL_**. Она служит источником истины и позволяет проводить интроспекцию. Каждое поле сопоставляется с резолвером — функцией, возвращающей значение. Сервер обходит дерево запроса и вызывает резолверы для каждого поля.\n\n#### Сложности\n\n * **Проблема N+1.** Резолверы работают независимо, и без оптимизации (батчинга) это приводит к множеству запросов к базе\n * **Кэширование.** Стандартные **_HTTP_** -механизмы (**_CDN_** , **_ETag_**) не работают, так как всё идёт через **_POST_**. Требуется нормализованный кэш на клиенте\n * **Безопасность.** Клиент может отправить глубоко вложенный запрос, перегружающий базу. Нужны ограничение глубины и анализ сложности\n * **Ошибки.** Сервер почти всегда возвращает статус `200`, а сами ошибки помещаются в массив `errors` рядом с частичными данными. Это ломает мониторинг, основанный на кодах состояния **_HTTP_**\n * **Инфраструктура.** Загрузка файлов, **_rate limiting_** и наблюдаемость требуют отдельного инфраструктурного слоя\n\n\n\n#### Где эффективен\n\nТехнология оправдывает себя при множестве разнообразных клиентов со сложными требованиями к данным. Для одного клиента с простыми ресурсами проще взять **_REST_**. Для внутреннего межсервисного взаимодействия с жёсткими контрактами и минимальной задержкой лучше подходит **_gRPC_**.\n\n### Что представляет собой AshGraphQL\n\nНесмотря на то что **_GraphQL_** пока уступает в популярности другим подходам, его интеграция с **_Ash_** сделана очень качественно. Технологию легко внедрить в проекты на базе **_Ash_**.\n\nДля начала добавьте библиотеку в проект:\n\n\n\n mix igniter.install ash_graphql\n\n\nЗатем в модуле схемы укажите используемые домены:\n\n\n\n use AshGraphql, domains: [Your.Domain1, Your.Domain2]\n\n\n### Настройка ресурсов и доменов\n\nЧтобы ресурс стал доступен через **_GraphQL_** , подключите расширение `AshGraphql.Resource` и опишите базовый блок конфигурации:\n\n\n\n defmodule CourseHub.Courses.Lesson do\n use Ash.Resource,\n extensions: [\n AshGraphql.Resource\n ]\n\n graphql do\n type :lesson\n end\n\n # ... остальная логика ресурса\n end\n\n\nВ модуле домена определите операции (ручки) через расширение `AshGraphql.Domain`:\n\n\n\n defmodule CourseHub.Courses do\n use Ash.Domain,\n extensions: [\n AshGraphql.Domain\n ]\n\n graphql do\n queries do\n get CourseHub.Courses.Lesson, :get_lesson, :read\n read_one CourseHub.Courses.Lesson, :most_popular_lesson, :most_popular\n list CourseHub.Courses.Lesson, :list_lessons, :read\n end\n\n mutations do\n create CourseHub.Courses.Lesson, :create_lesson, :create\n update CourseHub.Courses.Lesson, :update_lesson, :update\n destroy CourseHub.Courses.Lesson, :destroy_lesson, :destroy\n end\n end\n end\n\n\nОписывать эти ручки можно не только в модуле домена, но и прямо внутри ресурса.\n\nКроме того, вы можете создавать собственные действия для кастомных ответов. Пример:\n\n\n\n graphql do\n type :lesson\n\n queries do\n action :random_lesson, :random_lesson\n end\n end\n\n actions do\n action :random_lesson, {:array, :struct} do\n constraints items: [instance_of: __MODULE__]\n\n run fn _input, _context ->\n # возвращаем список структур, как объявлено в типе действия\n Ash.read(__MODULE__)\n end\n end\n end\n\n\n#### Генерация схемы SDL\n\nКак и в **_AshJsonApi_** (_где**Swagger** генерируется автоматически после правок_), схема **_GraphQL_** обновляется схожим образом. Пропишите путь для сохранения в `use AshGraphql` через `generate_sdl_file:`. Файл будет автоматически генерироваться при каждом запуске `mix ash.codegen` (если включён `auto_generate_sdl_file?: true`):\n\n\n\n use AshGraphql,\n domains: [Domain1, Domain2],\n generate_sdl_file: \"priv/schema.graphql\",\n auto_generate_sdl_file?: true\n\n\n#### Создание подписок (Subscriptions)\n\nМожно настроить получение данных в реальном времени через механизм подписок. Для этого используется конфигурация **_Absinthe_** :\n\n\n\n # в файле схемы Absinthe\n subscription do\n field :field, :type_name do\n config(fn\n _args, %{context: %{current_user: %{id: user_id}}} ->\n {:ok, topic: user_id, context_id: \"user/#{user_id}\"}\n\n _args, _context ->\n {:error, :unauthorized}\n end)\n\n resolve(fn args, _, resolution ->\n AshGraphql.Subscription.query_for_subscription(\n YourResource,\n YourDomain,\n resolution\n )\n |> Ash.Query.filter(id == ^args.id)\n |> Ash.read(actor: resolution.context.current_user)\n end)\n end\n end\n\n\n#### Мониторинг\n\nДля глубокого погружения рекомендую официальное руководство по мониторингу Ash. Здесь кратко опишем механизмы трассировки и события телеметрии, которые становятся доступны с этим расширением.\n\nМожно определить собственный трассировщик на уровне домена — его настройки будут иметь приоритет над глобальным параметром `config :ash, :tracer`:\n\n\n\n graphql do\n tracer MyApp.Tracer\n end\n\n\n#### Трассировка (Traces)\n\nОперации резолверов **_GraphQL_** и базовых пакетных загрузчиков данных оборачиваются в диапазоны (**_spans_**) с соответствующими именами. Для удобства к ним добавляется метаданное `source: :graphql`, что позволяет фильтровать или комментировать эти данные в системе мониторинга.\n\n#### Телеметрия (Telemetry)\n\nМодуль **_AshGraphql_** публикует события телеметрии, каждое из которых сопровождается суффиксами `:start` и `:stop`. События начала (`:start`) содержат измерение `system_time`, а события завершения (`:stop`) — `system_time` и длительность выполнения `duration`. Все временные показатели представлены в нативных единицах времени.\n\nСписок генерируемых событий:\n\n * **`[:ash,<имя_домена>, :gql_mutation]`** — выполнение мутации. Для детализации используйте метаданные `resource_short_name` и `mutation` (или `action`)\n * **`[:ash,<имя_домена>, :gql_query]`** — выполнение запроса. Для детализации используйте метаданные `resource_short_name` и `query` (или `action`)\n * **`[:ash,<имя_домена>, :gql_relationship]`** — разрешение связи. Для детализации используйте метаданные `resource_short_name` и `relationship`\n * **`[:ash,<имя_домена>, :gql_calculation]`** — разрешение вычисления. Для детализации используйте метаданные `resource_short_name` и `calculation`\n * **`[:ash,<имя_домена>, :gql_relationship_batch]`** — пакетное разрешение связей загрузчиком данных. Для детализации используйте метаданные `resource_short_name` и `relationship`\n * **`[:ash,<имя_домена>, :gql_calculation_batch]`** — пакетное разрешение вычислений загрузчиком данных. Для детализации используйте метаданные `resource_short_name` и `calculation`\n\n\n\n### Отзыв\n\nЧестно говоря, я нечасто прибегал к такому формату взаимодействия, но из того немногого опыта, что у меня есть, скажу одно — это действительно удобно.\n\nㅤ\n\n\nㅤ\n\n\n## 😳 AshTypescript — скрытая жемчужина\n\nМы уже познакомились с **_AshJsonApi_** и **_AshGraphQL_** , и это был полезный опыт. Эти два подхода — своего рода индустриальный стандарт веб-разработки, и особых сюрпризов для опытных специалистов в них нет. Но у **_Ash_** есть «козырная карта» — AshTypescript.\n\nПредставьте, что вы хотите взаимодействовать с фронтендом с типобезопасностью уровня **_gRPC_** , сохраняя структуру эндпоинтов как в **_AshJsonApi_** и гибкость работы с данными как в **_AshGraphQL_**. Именно это и решает **_AshTypescript_**.\n\n### Принцип работы\n\n 1. **Определение структуры.** Вы описываете домен и ресурсы, указывая нужные действия (**_actions_**) и атрибуты\n 2. **Подключение расширений.** В ресурс добавляется `AshTypescript.Resource`, а в домен — `AshTypescript.Rpc`\n 3. **Настройка ресурса.** В блоке `typescript` внутри ресурса задаётся его имя для генерации\n\n\n\n\n defmodule CourseHub.Courses.Lesson do\n use Ash.Resource,\n domain: CourseHub.Courses,\n extensions: [AshTypescript.Resource] # Подключаем расширение\n\n # Задаём имя типа для сгенерированного RPC-файла\n typescript do\n type_name \"Lesson\"\n end\n\n # Стандартные CRUD-действия\n actions do\n defaults [:read, :create, :update, :destroy]\n end\n\n # Описываем атрибуты ресурса\n attributes do\n uuid_primary_key :id\n attribute :title, :string, allow_nil?: false\n attribute :description, :string, default: \"Какой-то текст!\"\n end\n\n # Описываем связь с Course\n relationships do\n belongs_to :course, CourseHub.Courses.Course\n end\n end\n\n\n 1. **Привязка RPC-методов.** В модуле домена вы определяете, какие методы будут доступны фронтенду, и связываете их с действиями ресурса.\n\n\n\n\n defmodule CourseHub.Courses do\n use Ash.Domain, extensions: [AshTypescript.Rpc]\n\n # Публичные RPC-методы для фронтенда\n typescript_rpc do\n resource CourseHub.Courses.Lesson do\n rpc_action :list_lessons, :read\n rpc_action :create_lesson, :create\n rpc_action :get_lesson, :get\n rpc_action :delete_lesson, :destroy\n rpc_action :update_lesson, :update\n end\n end\n end\n\n\n 1. **Генерация кода.** Запустив `mix ash_typescript.codegen` (_или общую`mix ash.codegen`_), вы получите сгенерированные файлы по пути из конфигурации. Настройки лежат в `config/config.exs`:\n\n\n\n\n config :ash_typescript,\n ash_domains: [\n Backend.CourseHub\n ],\n output_file: \"assets/js/ash_rpc.ts\", # Путь для сохранения сгенерированного кода\n run_endpoint: \"/rpc/run\", # Эндпоинт для отправки запросов\n validate_endpoint: \"/rpc/validate\", # Эндпоинт для валидации данных по схеме\n input_field_formatter: :camel_case, # Формат имён полей на входе\n output_field_formatter: :camel_case, # Формат имён полей на выходе\n require_tenant_parameters: false, # Мультитенантность\n generate_zod_schemas: true, # Генерация схем для Zod\n generate_phx_channel_rpc_actions: false,\n generate_validation_functions: true,\n zod_import_path: \"zod\",\n zod_schema_suffix: \"ZodSchema\",\n phoenix_import_path: \"phoenix\"\n\n\n> **Zod** — библиотека для описания и проверки структуры данных. Она позволяет создавать схемы и автоматически валидировать по ним входящие данные.\n>\n> **Ключевые преимущества Zod:**\n>\n> * **Ориентированность на TypeScript** — автоматически выводит типы\n> * Интуитивно понятный и легко читаемый API\n> * Поддержка сложных правил валидации (_объединения типов, пересечения, уточнения_)\n> * Высокая производительность и минимальный вес\n>\n\n\nНачиная с новых версий, AshTypescript генерирует не один монолитный файл, а несколько: помимо `ash_rpc.ts` появляются общие типы (`ash_types.ts`) и схемы **_Zod_** (`ash_zod.ts`), которые импортируются из общего места.\n\nВ итоге вам остаётся лишь передать сгенерированные файлы на фронтенд. Разработчики клиента просто вызывают методы, например `listLessons()`, без ручного описания **_API_**. Это делает интеграцию невероятно простой и удобной.\n\n### Механизмы сортировки, фильтрации и проекции полей\n\nВ стандартной серверной разработке приходится вручную конструировать **_SQL_** -запросы для сортировки, фильтрации и выборки, чтобы избежать избыточной передачи данных. В **_AshTypescript_** реализованы те же возможности, что и в **_AshJsonApi_** , — гибко и типобезопасно:\n\n\n\n // CREATE\n const createLessonResponse = await createLesson({\n fields: [\"id\", \"title\"],\n input: { title: \"Elixir lesson about Ash\", priority: \"very high!\" },\n headers\n });\n\n if (!createLessonResponse.success) {\n console.error(\"Create failed:\", createLessonResponse.errors);\n return;\n }\n\n const lessonId = createLessonResponse.data.id;\n\n // READ (single)\n const getLesson = await getLesson({\n fields: [\"id\", \"title\", \"description\", { course: [\"name\"] }],\n input: { id: lessonId },\n headers\n });\n\n // READ (list)\n const lessons = await listLessons({\n fields: [\"id\", \"title\"],\n headers\n });\n\n // UPDATE\n const updateLessonResult = await updateLessson({\n fields: [\"id\", \"title\"],\n identity: lessonId,\n input: { title: \"Yoo chill im just a vessel!\" },\n headers\n });\n\n // DELETE\n const deleteLessonResponse = await deleteLesson({\n identity: lessonId,\n headers\n });\n\n\n### Преимущества использования AshTypescript\n\n 1. **Гарантия целостности контракта.** Не нужно беспокоиться о рассинхроне интерфейса, как это бывает с «ручным» **_gRPC_**. Единственный источник истины — генератор _.ts_ -файлов\n 2. **Автоматизация создания API.** Отпадает ручное написание методов и эндпоинтов. Достаточно описать ресурс и `typescript_rpc` в домене и дать методам уникальные имена\n 3. **Минимизация шаблонного кода.** Встроенные инструменты для сортировки, фильтрации и выборки полей (_**select** /проекции_) делают работу бесшовной\n 4. **Простая интеграция с другими командами.** Сгенерированные файлы (**_ash_rpc.ts_ , _ash_types.ts_ , _ash_zod.ts_**) можно передать коллегам — они начнут работу немедленно, не тратя время на изучение документации\n\n\n\n### Впечатления\n\nУзнав об этом способе организации взаимодействия между сервером и клиентом, я вновь испытал энтузиазм к программированию. Ручное написание эндпоинтов монотонно, а описание схем для **_GraphQL_** отнимает много времени. Декларативное же описание **_actions_** на бэкенде и простой вызов методов на фронтенде — это быстро и удобно. Такой подход убирает до 80% лишнего кода и многократно ускоряет разработку.\n\nㅤ\n\n\nㅤ\n\n\n## 🧮 Сравнение трёх подходов\n\nУ всех трёх способов один фундамент — ваши **_actions_**. Меняется только «витрина», через которую логика выходит наружу. Отсюда и главный вывод: выбор способа не запирает вас в нём навсегда — при необходимости можно добавить второй контракт поверх той же логики.\n\nКритерий | AshJsonApi (REST / JSON:API) | AshGraphQL | AshTypescript\n---|---|---|---\n**Тип контракта** | REST по стандарту JSON:API | GraphQL-схема (SDL) | Сгенерированные `.ts`-файлы (RPC)\n**Источник истины** | ваши _actions_ + автоген OpenAPI | ваши _actions_ + автоген SDL | ваши _actions_ + автоген TypeScript\n**Как клиент задаёт запрос** | query-параметры (`filter`, `sort`, `include`) | GraphQL-запрос с деревом полей | типизированные вызовы (`fields`, `filter`, `sort`)\n**Гибкость выборки** | высокая | максимальная | высокая\n**Индустриальный стандарт** | да, повсеместно | да, но нишевый | нет, специфичен для стека Ash + TS\n**Типобезопасность на фронте** | через кодоген из OpenAPI | через кодоген из схемы | нативная, из коробки\n**Реалтайм** | нет из коробки | да (subscriptions) | да (Phoenix channels)\n**HTTP-кэширование** | работает (GET) | затруднено (POST) | зависит от реализации\n**Порог входа на фронте** | низкий | средний/высокий | низкий (для TypeScript-команд)\n**Идеально для** | публичные и внешние API | много разных клиентов со сложными запросами | full-stack Elixir + TypeScript\n\n**Как выбирать на практике:**\n\n * **AshJsonApi** — когда нужен привычный **_REST_** , публичный или внешний **_API_** , кэширование на уровне **_HTTP_** и максимальная совместимость с любыми клиентами. Самый «нейтральный» выбор\n * **AshGraphQL** — когда клиентов много и у них разные, часто вложенные требования к данным, а под-/пере-выборка (**_over/under-fetching_**) реально мешает. Готовьтесь платить за это батчингом против N+1, ограничением сложности запросов и клиентским кэшем\n * **AshTypescript** — когда и бэкенд, и фронтенд ваши, и фронтенд на **_TypeScript_**. Даёт сквозную типобезопасность без ручного контракта: переименовали поле в **_Elixir_** — сразу получили ошибку компиляции в **_TS_**\n\n\n\nЕсли сомневаетесь и клиент один и относительно простой — начните с **_AshJsonApi_**. Если позже понадобится **_GraphQL_** или типобезопасный **_RPC_** , вы просто добавите ещё один контракт поверх тех же **_actions_** , не переписывая логику.\n\nㅤ\n\n\nㅤ\n\n\n## 😴 Заключение\n\nМир **_Ash_** даёт не просто удобный формат работы с БД и отсутствие бойлерплейта — он позволяет делать приложения быстро, качественно и с удовольствием. На полное освоение этих методов уйдёт чуть больше времени, но оно окупится не раз и здорово поможет в ваших проектах. Успехов!\n\nㅤ\n\n\nㅤ\n\n\n## 🥴 От автора\n\nСпасибо большое за интерес к этой статье! Надеюсь, она помогла разобраться, что представляет собой **_AshJsonApi_** , **_AshGraphQL_** , **_AshTypescript_** и зачем они используются!\n\nЕсли эта статья пришлась вам по душе и хочется ещё материалов такого плана, присоединяйтесь ко мне в моём телеграм-канале, где выкладываю обзоры книг, публикации по **_Elixir_** , переводы технической литературы и интересные новости!",
"title": "Ash: способы взаимодействия — AshJsonApi, AshGraphQL и AshTypescript"
}