Примечание переводчика: хотелось бы предоставить читателям еще один повод взглянуть на замечательный язык программирования Dart, на этот раз речь пойдёт о том как быстро и легко создавать RESTful API. Для тех, кто не в курсе, здесь понятно описано что это такое.
А тем, кто в курсе, добро пожаловать под кат.
С чего начать
В pub есть отличная библиотека, называется RPC. Это легковесный пакет для создания серверного RESTful API на Dart. И это всё, что нам понадобится.
Давайте начнём с добавления зависимости к нашему проекту в pubspec.yaml:
dependencies:
rpc: "^0.5.5"
Потом вызовем pub get чтобы загрузить пакеты в наш проект.
Теперь можно импортировать.
import 'package:rpc/rpc.dart';
Создаём класс API
Нам нужно создать класс API который будет содержать в себе наш API. Это Dart класс со специальной аннотацией @ApiClass из библиотеки RPC. Аннотация имеет один обязательный параметр — version, означающий версию предоставляемого API. Например v1. Вы так же можете добавить другие параметры, например, имя, чтобы иметь возможность переименовать API.
@ApiClass(version: 'v1')
class MyApi {
// Здесь будет содержимое класса
}
Методы API
Мы хотим предоставить возможность делать GET, POST, DELETE и т.д. запросы. Их все мы поместим класс API со специальной аннотацией. Методы могут быть помещены напрямую в API класс или можно использовать API ресурсы.
Методы в классе
Здесь мы создаём методы GET для animals и POST для animal. Каждый метод должен иметь аннотацию @ApiMethod с обязательным параметром path, который указывает путь для вызова нашего метода. Так же можно указать параметр, который укажет тип метода(GET,POST и т.д.). Если этот параметр не указать, то по умолчанию устанавливается GET.
@ApiClass(version: 'v1')
class MyApi {
List _animals = [];
@ApiMethod(path: 'animals')
List<Animal> getAnimals() => _animals;
@ApiMethod(path: 'animals', method: 'POST')
Animal postAnimal(Animal animal) {
_animals.add(animal);
return animal;
}
}
Здесь мы используем List для хранения animals. И предоставляем два метода: getAnimals(), который возвращает список всех животных и postAnimal, который добавляет новую запись animal в список animals. Естественно мы можем расширить данный пример другими методами API.
Чтобы это всё работало, нам понадобится класс Animal:
class Animal {
int id;
String name;
int numberOfLegs;
}
Ресурсы API
Обычно мы хотим разделить наш API на ресурсы, которые предоставляют конкретные API методы. Например, мы хотим иметь один ресурс для API для Animals и один для Person. С помощью библиотеки RPC мы можем сделать это легко.
Создаём класс ресурса и перемещаем туда все связанные с ресурсом методы:
class AnimalResource {
List _animals = [];
@ApiMethod(path: 'animals')
List<Animal> getAnimals() => _animals;
@ApiMethod(path: 'animals', method: 'POST')
Animal postAnimal(Animal animal) {
_animals.add(animal);
return animal;
}
}
Затем создаём экземпляр AnimalResource в нашем классе API, над которым есть аннотация @ApiResource:
@ApiClass(version: 'v1')
class MyApi {
@ApiResource()
AnimalResource resource = new AnimalResource();
}
Тестируем API
Чтобы использовать или протестировать наше API, нам надо создать скрипт сервера в папке bin. Нам надо создать здесь ApiServer, добавить туда наш API и создать HttpServer который будет слушать localhost:8080.
library my_server;
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:rpc/rpc.dart';
import '../lib/server/api.dart';
final ApiServer apiServer = new ApiServer(prettyPrint: true);
main() async {
Logger.root..level = Level.INFO..onRecord.listen(print);
apiServer.addApi(new MyApi());
HttpServer server = await HttpServer.bind(InternetAddress.ANY_IP_V4, 8080);
server.listen(apiServer.httpRequestHandler);
print('Server listening on http://${server.address.host}: ${server.port}');
}
Теперь запускаем Dart скрипт в bin который запускает сервер. Теперь наш API доступен по адресу localhost:8080/myApi/v1. Если мы хотим получить список животных, то создаём GET запрос на localhost:8080/myApi/v1/animals. Или же можем добавить запись сделав POST запрос на тот же адрес localhost:8080/myApi/v1/animals.
Протестировать POST запрос немного сложнее, т.к. для этого надо установить такой инструмент как curl или расширение для хром Postman или Advanced REST client.
Вот и всё, серверный API был создан всего за несколько минут.
Комментарии (23)
NorthDakota
11.01.2016 18:59+7Астрологи объявили неделю API на хабре.
Количество статей про RESTful API увеличено вдвое
bromzh
11.01.2016 19:05А в чём преимущества перед другими решениями?
Как настроить сериализацию/десериализацию например, для JSON? Как дела обстоят с БД? Есть ли для дарта ORM какие-то? И как у дарта с инструментами для разработки, фреймворками и библиотеками?
Так-то я лучше возьму, например, Eve или просто Flask + плагины, если надо быстро. Или какое-нибудь решение на java/scala. И возьму не только, потому что знаю их хорошо, но ещё и потому что у эти языков очень богатая инфраструктура, есть куча материала в сети и есть приличные IDE для них.
Vilyx
11.01.2016 19:32-5Уточните перед какими решениями вы хотите получить преимущества?
JSON — JSON.decode/encode
БД MySQL — библиотека SQLJocky, Mongo — Mongo-dart и Objectory ORM.
IDE — Webstorm как идеальный вариант, Atom как бесплатный.
По остальным вопросам есть моя же статья: Другой ответ на вопрос «Нужен ли мне Dart?».
Dart лучше того же GWT хотя бы тем, что тестировать можно не собирая проект, Dartium запускает Dart-код прямо в браузере. Если не брать в расчет GWT, то преимущество в том, что и клиент и сервер можно разработать на одном языке. Если сравнивать с JS, то, на мой взгляд, более приятный синтаксис и более удобный процесс разработки, про процесс рассказано в статье Сравнение процесса перехода Angular2 приложения до версии beta.0 на языках Dart и TypeScript.monolithed
11.01.2016 23:53+2Dart лучше того же GWT
А еще лучше курить чем нюхать клей.
Если не брать в расчет GWT, то преимущество в том, что и клиент и сервер можно разработать на одном языке.
Трансляторов в JS код как грязи.
Если сравнивать с JS, то, на мой взгляд, более приятный синтаксис
Вы про ES 2015 что-нибудь слышали?
более удобный процесс разработки
Опять же, лично для вас.
PS: пожалуйста хоть как-то аргументируйте свои выводы.Vilyx
12.01.2016 00:42-4тестировать можно не собирая проект
Это не аргумент?
Трансляторов в JS код как грязи.
Дальше что?
Вы про ES 2015 что-нибудь слышали?
Слышал и видел, а вы, видимо, видите только то, что хотите увидеть, я русским языком написал «на мой взгляд».
более удобный процесс разработки
Не лично для меня, для приличия зайдите по ссылке и прочитайте статью хотя бы по диагонали и отметьте для себя, что это перевод и что про удобство не я первый написал.
Опять же, лично для вас.
P.S. читайте внимательнее прежде чем писать.monolithed
12.01.2016 16:13-1Я писал некоторое время на Dart, и сейчас могу с полной уверенностью сказать, что тот стек технологий, который у меня сейчас просто унижает ваш Dart React, DevTools for Redux, React Developer Tools, Webpack, Wallaby, Сhimp и т.д.
Не лично для меня, для приличия зайдите по ссылке и прочитайте статью хотя бы по диагонали и отметьте для себя, что это перевод и что про удобство не я первый написал.
Судя по вашим постам вы являетесь ярым адептом Dart'a, поэтому лично для вас.
Vilyx
12.01.2016 20:49-2просто унижает ваш Dart
Отвечу на это вашей же цитатой:
PS: пожалуйста хоть как-то аргументируйте свои выводы.
Судя по вашим постам вы являетесь ярым адептом Dart'a, поэтому лично для вас.
Т.е. по вашему тот факт, что я пишу только о Dart, делает меня ярым адептом и нивелирует все те положительные отзывы, которые приведены в моих статьях или переводах и еще больше положительных отзывов и статей, которых нет среди моих статей и переводов, так получается? Это как утверждать, что врачи пишущие о пользе прививок делают сами прививки бесполезными.
bromzh
12.01.2016 01:41JSON — JSON.decode/encode
Как у объекта отметить поля игнорируемыми при (де)сериализации объекта? Как переименовать поле? Как добавить метод как сериализуемое поле? Ну и т.д.
Просто на деле часто бывает так: сперва данные АПИ простые и легко перегоняются из и в JSON (ну или xml, не важно). Но рано или поздно наступает момент, когда нужно настроить отображение модели в JSON и правила восстановления модели из него. При этом, не хочется плодить лишние сущности (например, создать отдельный класс для отображения). И если в языке нет готовой библиотеки-маппера с возможностью настройки входного или выходного формата, то можно начать жалеть о своём выборе.
В качестве ОРМ для реляционных баз я увидел это. Но там даже нет отношений. ОРМ для монги умерло.
Как данные-то хранить? Писать запросы и парсить ответы вручную? Так легче взять питон с sqlalchemy и сэкономить кучу времени.
Уточните перед какими решениями вы хотите получить преимущества?
JSON: Ну, например, Jackson для java. Аннотациями можно настроить очень многое. На крайняк, можно написать свой плагин/модуль/datatype и т.д. Часто жалею, что аналогичной либы без зависимостей от веб-фреймворка нет для питона (или я искал плохо).
DB: Например, SQLAlchemy. sqlalchemy-core не реализует ОРМ, но предоставляет унифицированный интерфейс для подключения к БД и приятный DSL для формирования запросов. ORM тоже есть, и он довольно гибкий. Ещё пример — Hibernate или EclipseLink. Помимо кучи фич из JPA (который они реализуют), каждая имеет вдобавок свои полезные плюшки.
Так есть ли удобная либа/фреймворк для persistence-слоя? Будь то ОРМ, просто удобный инструмент для простой работой с реляционными БД или что-то для популярных nosql-баз? Понятно, что хибернейт или алхимия — большие и зрелые проекты, но есть ли в дарте хоть что-то приближённое?
Просто вот такие туторы в 10-30 строк кода хороши только в теории. Всё красиво, ёмко и просто. Но на практике малораспространённые языки/технлогии обычно сосут из-за отсутствия хороших библиотек. Может я и ошибаюсь, но тогда хочется увидеть пример сложного апи-сервиса, а не хелловорда.Vilyx
12.01.2016 03:22-1Для JSON есть библиотека dartson, она умеет всё, что вы упомянули.
Про БД затрудняюсь ответить, не моя вотчина. Скорее всего вы правы и Dart не имеет зрелого решения на данный момент.
xamd
11.01.2016 23:43+2Такое ощущение, что Вы перевели Getting Started к библиотеке rpc.
Вообще, вот то же самое в 18 строчках на JS (или 14 не считая пустые). Я не могу говорить за всех, но имхо, этот код понятнее и проще.
const app = require('express')(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); var animals = []; const findAnimalById = (id) => animals.filter(a => a.id === Number(id))[0]; const removeAnimal = (id) => animals = animals.filter(a => a.id !== Number(id)); const makeAnimal = (params) => animals.push(Object.assign({ id: animals.length + 1 }, params)) && animals[animals.length - 1]; app.get('/list', (req, res) => res.json(animals)); app.get('/', (req, res) => res.json(findAnimalById(req.query.id))); app.post('/', (req, res) => res.json(makeAnimal(req.body))); app.delete('/', (req, res) => res.json(removeAnimal(req.query.id))) app.listen(3000);
Vilyx
11.01.2016 23:52-4Я с вами не согласен, мне проще и понятнее два файлика общим объёмом 25 строк:
import 'dart:io'; import 'package:logging/logging.dart'; import 'package:rpc/rpc.dart'; import '../lib/server/api.dart'; final ApiServer apiServer = new ApiServer(prettyPrint: true); main() async { apiServer.addApi(new MyApi()); HttpServer server = await HttpServer.bind(InternetAddress.ANY_IP_V4, 8080); server.listen(apiServer.httpRequestHandler); }
и
@ApiClass(version: 'v1') class MyApi { List _animals = []; @ApiMethod(path: 'animals') List<Animal> getAnimals() => _animals; @ApiMethod(path: 'animals', method: 'POST') Animal postAnimal(Animal animal) { _animals.add(animal); return animal; } }
lain8dono
12.01.2016 03:45-1package main import ( "encoding/json" "flag" "net/http" "strconv" ) var addr = flag.String("http", ":3000", "http service address") type Animal struct { ID int Name string } func main() { animals := []Animal{ {0, "python"}, {1, "sad panda"}, {2, "candy bear"}, {3, "camel"}, {4, "cat is fine too"}, } flag.Parse() http.HandleFunc("/list", func(w http.ResponseWriter, _ *http.Request) { json.NewEncoder(w).Encode(animals) }) http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { defer func() { if err := recover(); err != nil { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } }() id, _ := strconv.Atoi(req.FormValue("ID")) switch req.Method { case "GET": json.NewEncoder(w).Encode(animals[id]) case "POST": animals = append(animals, Animal{ len(animals), req.FormValue("Name")}) case "DELETE": animals = append(animals[:id], animals[id+1:]...) } }) panic(http.ListenAndServe(*addr, nil)) }
А вообще различные примеры hello world для http можно посмотреть тут
napa3um
12.01.2016 11:02REST, по-сути, является математической формализацией протокола передачи состояния между двумя компонентами информационной системы. Знать HTTP, но не понимать REST это как знать SQL, но не понимать реляционной модели данных (реляционной алгебры/исчисления, видов нормализации данных, т.д.)
Однако же в данной заметке вообще не идёт речи о REST. Тут идёт речь о том, как с помощью конкретной RPC-библиотеки можно описать обработчики запросов. Это как статья «Строим ракету» рассказывала бы о том, как скачать кряк на SolidWorks. Пикантности добавляет тот факт, что REST как архитектурный принцип как раз «возвражает» концепции RPC, вносит свои ограничения на виды запросов, REST-API — это очень конкретное подмножество RPC-API. (Не очень понимаю, почему библиотека называется именно RPC, а не HTTP, или она действительно абстрагирована от низлежащих протоколов и может работать над любым?)
Вся эта мода на аббревиатуры, термины, «новые принципы» и т.п. даёт ощущение нахождения в тренде, в теме. Но если вы сами не столкнулись с проблемами, которые решает очередная аббревиатура или принцип, если не поняли этой проблемы, то стОит пока воздержаться от внедрения принципа в свой проект.
Vilyx
«Я человек простой, вижу минус, жму его.»
@Минусатор
Vilyx
Я понимаю чем плох комментарий, но хоть бы один человек объяснил чем плоха статья.
evnuh
Если вы правда не понимаете, то ответ прост — никому не интересно читать статью про «скачиваем библиотеку, по мануалу к ней пишем 10 строчек кода, готово».
Vilyx
Под «никому» вы подразумеваете себя? Или тех, кто положительно оценивает и добавляет в закладки эту статью?
evnuh
Очевидно, тех, кто положительно оценивает и добавляет в закладки эту статью. Вы про кого спрашивали? Я про них и ответил.
Vilyx
Я попросил объяснять негативную оценку, а не выдавать своё мнение за мнение всех. Говоря «никому не нравится» вы подразумеваете всех, на это я вам саркастично ответил указав на вашу ошибку в суждении.