Примечание переводчика: хотелось бы предоставить читателям еще один повод взглянуть на замечательный язык программирования 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)


  1. Vilyx
    11.01.2016 18:29
    -18

    «Я человек простой, вижу минус, жму его.»
    @Минусатор


    1. Vilyx
      11.01.2016 19:58
      -11

      Я понимаю чем плох комментарий, но хоть бы один человек объяснил чем плоха статья.


      1. evnuh
        11.01.2016 20:05
        +9

        Если вы правда не понимаете, то ответ прост — никому не интересно читать статью про «скачиваем библиотеку, по мануалу к ней пишем 10 строчек кода, готово».


        1. Vilyx
          11.01.2016 20:07
          -12

          Под «никому» вы подразумеваете себя? Или тех, кто положительно оценивает и добавляет в закладки эту статью?


          1. evnuh
            11.01.2016 20:22
            +6

            Очевидно, тех, кто положительно оценивает и добавляет в закладки эту статью. Вы про кого спрашивали? Я про них и ответил.


            1. Vilyx
              11.01.2016 21:04
              -12

              Я попросил объяснять негативную оценку, а не выдавать своё мнение за мнение всех. Говоря «никому не нравится» вы подразумеваете всех, на это я вам саркастично ответил указав на вашу ошибку в суждении.


  1. NorthDakota
    11.01.2016 18:59
    +7

    Астрологи объявили неделю API на хабре.
    Количество статей про RESTful API увеличено вдвое


    1. monolithed
      11.01.2016 23:39
      +4

      Даже на богом забытых языках


  1. bromzh
    11.01.2016 19:05

    А в чём преимущества перед другими решениями?
    Как настроить сериализацию/десериализацию например, для JSON? Как дела обстоят с БД? Есть ли для дарта ORM какие-то? И как у дарта с инструментами для разработки, фреймворками и библиотеками?

    Так-то я лучше возьму, например, Eve или просто Flask + плагины, если надо быстро. Или какое-нибудь решение на java/scala. И возьму не только, потому что знаю их хорошо, но ещё и потому что у эти языков очень богатая инфраструктура, есть куча материала в сети и есть приличные IDE для них.


    1. Vilyx
      11.01.2016 19:33
      -1

      Промахнулся, я ответил вам чуть ниже.


  1. 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.


    1. monolithed
      11.01.2016 23:53
      +2

      Dart лучше того же GWT

      А еще лучше курить чем нюхать клей.

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

      Трансляторов в JS код как грязи.

      Если сравнивать с JS, то, на мой взгляд, более приятный синтаксис

      Вы про ES 2015 что-нибудь слышали?

      более удобный процесс разработки

      Опять же, лично для вас.

      PS: пожалуйста хоть как-то аргументируйте свои выводы.


      1. Vilyx
        12.01.2016 00:42
        -4

        тестировать можно не собирая проект
        Это не аргумент?
        Трансляторов в JS код как грязи.
        Дальше что?
        Вы про ES 2015 что-нибудь слышали?
        Слышал и видел, а вы, видимо, видите только то, что хотите увидеть, я русским языком написал «на мой взгляд».
        более удобный процесс разработки
        Опять же, лично для вас.
        Не лично для меня, для приличия зайдите по ссылке и прочитайте статью хотя бы по диагонали и отметьте для себя, что это перевод и что про удобство не я первый написал.

        P.S. читайте внимательнее прежде чем писать.


        1. monolithed
          12.01.2016 16:13
          -1

          Я писал некоторое время на Dart, и сейчас могу с полной уверенностью сказать, что тот стек технологий, который у меня сейчас просто унижает ваш Dart React, DevTools for Redux, React Developer Tools, Webpack, Wallaby, Сhimp и т.д.

          Не лично для меня, для приличия зайдите по ссылке и прочитайте статью хотя бы по диагонали и отметьте для себя, что это перевод и что про удобство не я первый написал.
          Судя по вашим постам вы являетесь ярым адептом Dart'a, поэтому лично для вас.


          1. Vilyx
            12.01.2016 20:49
            -2

            просто унижает ваш Dart
            Отвечу на это вашей же цитатой:
            PS: пожалуйста хоть как-то аргументируйте свои выводы.

            Судя по вашим постам вы являетесь ярым адептом Dart'a, поэтому лично для вас.
            Т.е. по вашему тот факт, что я пишу только о Dart, делает меня ярым адептом и нивелирует все те положительные отзывы, которые приведены в моих статьях или переводах и еще больше положительных отзывов и статей, которых нет среди моих статей и переводов, так получается? Это как утверждать, что врачи пишущие о пользе прививок делают сами прививки бесполезными.


    1. bromzh
      12.01.2016 01:41

      JSON — JSON.decode/encode

      Как у объекта отметить поля игнорируемыми при (де)сериализации объекта? Как переименовать поле? Как добавить метод как сериализуемое поле? Ну и т.д.
      Просто на деле часто бывает так: сперва данные АПИ простые и легко перегоняются из и в JSON (ну или xml, не важно). Но рано или поздно наступает момент, когда нужно настроить отображение модели в JSON и правила восстановления модели из него. При этом, не хочется плодить лишние сущности (например, создать отдельный класс для отображения). И если в языке нет готовой библиотеки-маппера с возможностью настройки входного или выходного формата, то можно начать жалеть о своём выборе.

      В качестве ОРМ для реляционных баз я увидел это. Но там даже нет отношений. ОРМ для монги умерло.
      Как данные-то хранить? Писать запросы и парсить ответы вручную? Так легче взять питон с sqlalchemy и сэкономить кучу времени.

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

      JSON: Ну, например, Jackson для java. Аннотациями можно настроить очень многое. На крайняк, можно написать свой плагин/модуль/datatype и т.д. Часто жалею, что аналогичной либы без зависимостей от веб-фреймворка нет для питона (или я искал плохо).
      DB: Например, SQLAlchemy. sqlalchemy-core не реализует ОРМ, но предоставляет унифицированный интерфейс для подключения к БД и приятный DSL для формирования запросов. ORM тоже есть, и он довольно гибкий. Ещё пример — Hibernate или EclipseLink. Помимо кучи фич из JPA (который они реализуют), каждая имеет вдобавок свои полезные плюшки.
      Так есть ли удобная либа/фреймворк для persistence-слоя? Будь то ОРМ, просто удобный инструмент для простой работой с реляционными БД или что-то для популярных nosql-баз? Понятно, что хибернейт или алхимия — большие и зрелые проекты, но есть ли в дарте хоть что-то приближённое?

      Просто вот такие туторы в 10-30 строк кода хороши только в теории. Всё красиво, ёмко и просто. Но на практике малораспространённые языки/технлогии обычно сосут из-за отсутствия хороших библиотек. Может я и ошибаюсь, но тогда хочется увидеть пример сложного апи-сервиса, а не хелловорда.


      1. Vilyx
        12.01.2016 03:22
        -1

        Для JSON есть библиотека dartson, она умеет всё, что вы упомянули.
        Про БД затрудняюсь ответить, не моя вотчина. Скорее всего вы правы и Dart не имеет зрелого решения на данный момент.


  1. 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);
    


    1. 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;
        }
      }
      


      1. xamd
        12.01.2016 00:57

        Ну, всем своё :) Только вроде 3 же файла? Ещё один с описанием <Animals>?


        1. Vilyx
          12.01.2016 01:25
          -4

          В данном конкретном случае да, в общем случае третьего файла может и не быть.


    1. lain8dono
      12.01.2016 03:45
      -1

      package 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 можно посмотреть тут


  1. napa3um
    12.01.2016 11:02

    REST, по-сути, является математической формализацией протокола передачи состояния между двумя компонентами информационной системы. Знать HTTP, но не понимать REST это как знать SQL, но не понимать реляционной модели данных (реляционной алгебры/исчисления, видов нормализации данных, т.д.)

    Однако же в данной заметке вообще не идёт речи о REST. Тут идёт речь о том, как с помощью конкретной RPC-библиотеки можно описать обработчики запросов. Это как статья «Строим ракету» рассказывала бы о том, как скачать кряк на SolidWorks. Пикантности добавляет тот факт, что REST как архитектурный принцип как раз «возвражает» концепции RPC, вносит свои ограничения на виды запросов, REST-API — это очень конкретное подмножество RPC-API. (Не очень понимаю, почему библиотека называется именно RPC, а не HTTP, или она действительно абстрагирована от низлежащих протоколов и может работать над любым?)

    Вся эта мода на аббревиатуры, термины, «новые принципы» и т.п. даёт ощущение нахождения в тренде, в теме. Но если вы сами не столкнулись с проблемами, которые решает очередная аббревиатура или принцип, если не поняли этой проблемы, то стОит пока воздержаться от внедрения принципа в свой проект.