[Dart, Flutter] gRPC + Dart, Сервис + Клиент, напишем? Часть 2

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
22-Июн-2021 13:46

Первая часть находится здесьПривет, я Андрей, работаю Flutter разработчиком в компании Финам.Давайте развивать сервис Umka основы которого мы заложили в первой части.Реализация отправки ответа на полученный вопросДля начала чуть изменим нашу "базу вопросов", таким образом, чтобы она содержала правильный ответ к каждому вопросу:
[
    {
        "id": 0,
        "text": "7 x 5 = ?",
        "answer": "35"
    },
    {
        "id": 1,
        "text": "12 x 13 = ?",
        "answer": "156"
    },
    {
        "id": 2,
        "text": "2 ** 5 = ?",
        "answer": "32"
    },
    {
        "id": 3,
        "text": "2 ** 10 = ?",
        "answer": "1024"
    },
    {
        "id": 4,
        "text": "2 ** 11 = ?",
        "answer": "2048"
    }
]
В файл lib/questions_db_driver.dart добавим метод getCorrectAnswerById, для получения корректного ответа по идентификатору вопроса и сделаем небольшой рефакторинг кода:
import 'dart:io';
import 'dart:convert';
import 'generated/umka.pb.dart';
final List<Question> questionsDb = _readDb();
List _getQuestionsList() {
  final jsonString = File('db/questions_db.json').readAsStringSync();
  return jsonDecode(jsonString);
}
List<Question> _readDb() => _getQuestionsList()
    .map((entry) => Question()
      ..id = entry['id']
      ..text = entry['text'])
    .toList();
String? getCorrectAnswerById(int questionId) {
  final jsonList = _getQuestionsList();
  final correctAnswer = jsonList.firstWhere(
    (element) => element['id'] == questionId,
    orElse: () => null,
  );
  return correctAnswer?['answer'];
}
В класс UmkaService добавим реализацию метода sendAnswer в котором:
  • получим из "базы" правильный ответ
  • если по какой-то причине "клиент" передал несуществующий идентификатор вопроса "выбросим" ошибку throw grpc.GrpcError.invalidArgument('Invalid question id!');
  • оценим ответ (за правильный в поле mark запишем 5, за неверный "влепим двойку") и вернём оценку "клиенту"
@override
  Future<Evaluation> sendAnswer(ServiceCall call, Answer request) async {
    print('Received answer for the question: $request');
    final correctAnswer = getCorrectAnswerById(request.question.id);
    if (correctAnswer == null) {
      throw grpc.GrpcError.invalidArgument('Invalid question id!');
    }
    final evaluation = Evaluation()
      ..id = 1
      ..answerId = request.id;
    if (correctAnswer == request.text) {
      evaluation.mark = 5;
    } else {
      evaluation.mark = 2;
    }
    return evaluation;
  }
Остальной код в файле lib/service.dart без изменений.Реализация метода sendAnswer на стороне клиентского приложения такая:
Future<void> sendAnswer(Student student, Question question) async {
    final answer = Answer()
      ..question = question
      ..student = student;
    print('Enter your answer: ');
    answer.text = stdin.readLineSync()!;
    final evaluation = await stub.sendAnswer(answer);
    print('Evaluation for the answer: ${answer.text} '
        '\non the question ${question.text}:'
        '\n$evaluation');
  }
  • создаем "экземпляр" класса Answer
  • добавляем в него текст ответа введённый "студентом" в терминал
  • отправляем ответ на "оценку"
  • дождавшись оценки от сервиса выводим её в консоль
Также чуть изменим метод обращения к сервису Umka callService:
Future<void> callService(Student student) async {
    final question = await getQuestion(student);
    await sendAnswer(student, question);
    await channel.shutdown();
  }
Здесь все просто:
  • запрашиваем у сервиса вопрос
  • отправляем на него ответ
  • закрываем соединение
Запускаем сервисДля запуска сервиса на localhost из директории проекта выполним команду:dart lib/service.dartВ окне терминала сервиса будут видны логи отправленных ответов. Чтобы завершить работу сервиса, можно нажать ctrl+c.Подключаемся к сервису терминальным клиентомКомандой dart lib/client.dart в соседнем окне терминала из папки проекта запустим нашего "клиента" и представим себя студентом, которому нужно ответить на полученный вопрос. Для этого читаем вопрос, и в терминал в виде числа вводим ответ. После этого нам "прилетит" оценка mark: 5 или mark: 2.Демонстрация вышеописанного:
Ошибки gRPCДавайте "заставим" вызов sendAnswer прислать нам ошибку. Для этого подменим question.id на нелепый, например так:
Future<void> callService(Student student) async {
    final question = await getQuestion(student);
    question.id = 777;
    await sendAnswer(student, question);
    await channel.shutdown();
  }
}
Сервис пришлет нам ошибку
Unhandled exception:
gRPC Error (code: 3, codeName: INVALID_ARGUMENT, message: Invalid question id!, details: [], rawResponse: null)
Демонстрация:
Ошибки, конечно же, требуют корректной обработки.Отправка потокa данных клиентскому приложениюДавайте добавим нашему сервису возможность обучать "студентов". Для этого организуем периодическую отправку вопросов клиентскому приложению вместе с ответом на него.Здесь нам и пригодится возможность gRPC отправлять stream c сервера "клиентам".Добавим к описанию нашего сервиса в файл protos/umka.proto один тип:
message AnsweredQuestion {
  Question question = 1;
  string answer = 2;
}
И один удалённый вызов:
rpc getTutorial(Student) returns (stream AnsweredQuestion) {}
Обратите внимание на аннотацию stream перед возвращаемым типом. Именно она "решает", что при вызове данной процедуры клиент будет получать поток данных, а не одиночный ответ.Теперь описание выглядит так:
Мы изменили описание сервиса, поэтому нужна "регенерация" gRPC Dart кода, и мы в папке проекта просто запускаем знакомую уже команду:protoc -I protos/ protos/umka.proto --dart_out=grpc:lib/generatedМожно заглянуть во вновь сгенерированный код и убедиться, что там появился новый "вызов" в виде метода getTutorial класса UmkaServiceBase в файле umka.pbgrpc.dart и класс AnsweredQuestion в файле umka.pb.dart.Код метода getTutorial для сервисаПерейдя в файл lib/service.dart обнаруживаем "ворчание" компилятора на то, что в классе UmkaService отсутствует реализация метода getTutorial.Напишем его код следующим образом:
@override
  Stream<AnsweredQuestion> getTutorial(
      ServiceCall call, Student request) async* {
    for (var question in questionsDb) {
      final answeredQuestion = AnsweredQuestion()
        ..question = question
        ..answer = getCorrectAnswerById(question.id)!;
      yield answeredQuestion;
      await Future.delayed(Duration(seconds: 2));
    }
  }
}
В Dart для того, чтобы функция возвращала стрим её нужно пометить ключевым словом async*. После этого в стрим объекты указанного типа Stream<AnsweredQuestion> отправляются с помощью ключевого слова yield.Метод getTutoriad по одному будет брать вопросы из "базы", отправлять их "студенту" в клиентское приложение, делать паузу 2 секунды для "подумать". Процесс будет повторяться пока не закончатся данные. После этого соединение, установленное при вызове метода getTutorial, будет прервано.Доработка клиентского приложенияИзменения здесь небольшие:
  • добавим в UmkaTerminalClient метод запроса урока takeTutorial,
  • изменим обращение к сервису сосредоточившись только на "уроке".
Future<void> takeTutorial(Student student) async {
    await for (var answeredQuestion in stub.getTutorial(student)) {
      print(answeredQuestion);
    }
  }
  Future<void> callService(Student student) async {
    await takeTutorial(student);
    await channel.shutdown();
  }
}
Dart конструкция await for позволяет удобно брать данные из потока до тех пор, пока поток не "иссякнет", после чего метод takeTutorial завершится. Текст вопросов с ответами на них просто печатаем в консоль.Запускаем dart lib/service.dart в одном терминальном окне и dart lib/client.dart в другом, и наблюдаем поток вопросов в клиентский терминал:
На этом вторая и завершим вторую часть.Мы поработали над развитием нашей системы добавив полезные "фичи":
  • Отправка на сервер ответа на полученный вопрос.
  • Получение обучающего материала в виде потока задачек с ответами.
Посмотрели как "прилетает" ошибка.Думаю, стало понятно, что в gRPC предусмотрена удобная возможность развития системы.До встречи в части №3 где мы продолжим добавлять полезные возможности нашему сервису на основе потока данных от клиента к сервису и двунаправленного потока данных.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_dart, #_flutter, #_dart, #_flutter, #_grpc, #_protobuf, #_dart (дарт), #_flatter (флаттер), #_dart, #_flutter
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 22-Ноя 19:39
Часовой пояс: UTC + 5