[Dart, Flutter] gRPC + Dart, Сервис + Клиент, напишем? Часть 3
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, я Андрей, работаю Flutter разработчиком в компании Финам.Продолжим развивать сервис Umka. ЭкзаменНа примере реализации кода для проведения "экзамена" мы познакомимся с возможностью технологии gRPC передавать данные в виде потока от клиентского приложения на сервис.Сценарий экзамена пусть будет таким:
- Ученик запрашивает у сервиса "Экзамен", представляющий из себя список вопросов.
- Ученик поочерёдно, на каждый вопрос, вводит в консоль ответ, который тут же отображается на серверной стороне сервиса.
- Если ответ верен, то сервис к экзаменационной оценке добавляет 1.
- После оценки последнего вопроса, сервис отправляет ученику результат.
Добавляем описаниеДополним описание сервиса одним типом Exam и двумя вызовами getExam, takeExam:
syntax="proto3";
message Student {
int32 id = 1;
string name = 2;
}
message Question {
int32 id = 1;
string text = 2;
}
message Answer {
int32 id = 1;
Student student = 2;
Question question = 3;
string text = 4;
}
message Evaluation {
int32 id = 1;
int32 answerId = 2;
int32 mark = 3;
}
message AnsweredQuestion {
Question question = 1;
string answer = 2;
}
message Exam {
int32 id = 1;
repeated Question questions = 2;
}
service Umka {
rpc getQuestion(Student) returns(Question) {}
rpc sendAnswer(Answer) returns(Evaluation) {}
rpc getTutorial(Student) returns (stream AnsweredQuestion) {}
rpc getExam(Student) returns (Exam) {}
rpc takeExam(stream Answer) returns(Evaluation) {}
}
Ключевое слово repeated говорит о том, что поле questions содержит список. Для языка Dart это будет List<Question>.В описании удаленного вызова rpc takeExam(stream Answer) returns(Evaluation) {} аннотация stream перед передаваемым типом Answer сообщает компилятору protoc, что код нужно сгенерировать таким образом, чтобы сервис от клиентского приложения получал ответы в виде потока данных.Выполним "регенерацию" базового Dart кода:protoc -I protos/ protos/umka.proto --dart_out=grpc:lib/generatedДополняем серверный код сервисаВ класс UmkaService добавим реализацию метода getExam для получения вопросов экзамена от сервиса:
@override
Future<Exam> getExam(ServiceCall call, Student request) async {
final exam = Exam()..id = 1;
exam.questions.addAll(questionsDb);
return exam;
}
Клиентскому приложению отправляются все вопросы из нашей "базы".Реализация метода takeExam потребует чуть больше кода:
@override
Future<Evaluation> takeExam(ServiceCall call, Stream<Answer> asnswers) async {
var score = 0;
await for (var answer in asnswers) {
final isCorrect = getCorrectAnswerById(answer.question.id) == answer.text;
print('Received an answer from ${answer.student.name}\n'
'for a question: ${answer.question.text}'
'answer: ${answer.text} is correct: $isCorrect');
if (isCorrect) {
score++;
}
}
print('The student: ${call.clientMetadata?['student_name']}'
' finished exam with the score: $score');
return Evaluation()
..id = 1
..mark = score;
}
}
- В await for получаем по одному ответы из потока, созданного клиентским приложением.
- В переменную var score = 0; добавляем балл за каждый правильный ответ.
- После отправки последнего ответа, "клиент" закроет стрим, а мы (сервис) вернём ему оценку.
- В консоль выводим полезную информацию по ходу "экзамена".
Кусочек кода call.clientMetadata?['student_name'] показывает пример, как можно получить дополнительную информацию из метаданных отправленных клиентским приложением. По своей сути, "под капотом", это один из заголовков HTTP/2 запроса.Дополняем код клиентаВ UmkaTerminalClient добавим метод takeExam:
Future<Evaluation> takeExam(Student student) async {
final exam = await stub.getExam(student);
final questions = exam.questions;
final answersStream = StreamController<Answer>();
final evaluationFuture = stub.takeExam(answersStream.stream,
options: CallOptions(metadata: {'student_name': '${student.name}'}));
for (var question in questions) {
final answer = Answer()
..question = question
..student = student;
print('Enter the answer for the question: ${question.text}');
answer.text = stdin.readLineSync()!;
answersStream.add(answer);
await Future.delayed(Duration(milliseconds: 1));
}
unawaited(answersStream.close());
return evaluationFuture;
}
- Получаем вопросы.
- Создаем поток для передачи ответов.
- Устанавливаем соединение, передав на сервис созданный "стрим" и метаданные: 'student_name'.
- На каждый полученный вопрос, по очереди, ученик вводит ответ в терминал.
- После формирования ответ добавляется в поток: answersStream.add(answer);.
- Отправив последний ответ, немедленно закрываем поток данных: unawaited(answersStream.close());
- Возвращаем Future с оценкой.
Функция unawated нужна чтобы сказать компилятору, что мы уверены в своих действиях и он не показывал "предупреждение".Она пока доступна только из библиотеки pedantic, поэтому добавим ее в зависимости:
Выполним команду dart pub get.Метод обращения к сервису напишем так:
Future<void> callService(Student student) async {
final evaluation = await takeExam(student);
print('${student.name}, your exam score is: ${evaluation.mark}');
await channel.shutdown();
}
- Дожидаемся окончания "экзамена", чтобы получить оценку.
- Выводим результат в консоль.
- Закрываем соединение с сервисом.
Запуск экзаменаВ разных терминальных окнах стартуем сервис и клиентское приложение
- Сервис: dart lib/service.dart
- Клиентское приложение: dart lib/client.dart
Демонстрация прохождения "экзамена":
"Студент" Ваня опечатался при ответе на третий вопрос и получил "четвёрку".Техническое интервьюМы подошли к самому интересному. Парой-тройкой десятков строчек кода мы реализуем чат, для проведения технического интервью.Для этого используем возможность gRPC осуществлять двунаправленную потоковую передачу данных от сервиса к клиентскому приложению и обратно в рамках одного HTTP/2 соединения.В описание сервиса добавим тип:
message InterviewMessage {
string name = 1;
string body = 2;
}
И удалённый вызов:
rpc techInterview(stream InterviewMessage) returns(stream InterviewMessage) {}
Вновь выполним "регенерацию" запустив в папке проекта команду:protoc -I protos/ protos/umka.proto --dart_out=grpc:lib/generatedКод сервисаНа верхний уровень файла lib/service.dart добавим константу с типчными вопросами "собеседования":
const interviewQuestions = [
'What was wrong in your previous job place?',
'Why do you want to work for Us?',
'Who do you see yourself in 5 years?',
'We will inform you about the decision. Bye!',
];
В класс UmkaService добавим вспомогательную функцию:
InterviewMessage _createMessage(String text, {String name = 'Interviewer'}) =>
InterviewMessage()
..name = name
..body = text;
А также напишем реализацию метода techInterview:
@override
Stream<InterviewMessage> techInterview(
ServiceCall call, Stream<InterviewMessage> interviewStream) async* {
var count = 0;
await for (var message in interviewStream) {
print('Candidate ${message.name} message: ${message.body}');
if (count >= interviewQuestions.length) {
return;
} else {
yield _createMessage(interviewQuestions[count++]);
}
}
}
- После получения первого сообщения от кандидата, что он готов к интервью, отправляем ему вопросы по одному.
- На каждый отправленный вопрос дожидаемся ответа.
- Получив ответ на последний вопрос, "выходим" — стрим interviewStream на стороне клиента будет закрыт.
Код клиентского приложения"На клиенте" код метода techInterview пусть будет такой:
Future<void> techInterview(String candidateName) async {
final candidateStream = StreamController<InterviewMessage>();
final interviewerStream = stub.techInterview(candidateStream.stream);
candidateStream.add(InterviewMessage()
..name = candidateName
..body = 'I am ready!');
await for (var message in interviewerStream) {
print('\nMessage from the ${message.name}:\n${message.body}\n');
print('Enter your answer:');
final answer = stdin.readLineSync();
candidateStream.add(InterviewMessage()..body = answer!);
}
unawaited(candidateStream.close());
}
- Создаём стрим final candidateStream = StreamController<InterviewMessage>();.
- Передаём candidateStream удаленному вызову, получая обратно interviewerStream.
- Отправляем информацию, что кандидат готов к интервью, чтобы завязать "диалог".
- Следим за вопросами поступающими в interviewerStream.
- Ответы вводим в терминал.
- Читаем введённые ответы и отправляем их на сервис.
- После завершения потока вопросов от сервиса, закрываем соединение.
К сервису на этот раз будем обращаться так:
Future<void> callService(Student student) async {
await techInterview(student.name);
await channel.shutdown();
}
Вот гифка демонстрирующая процесс "интервью". В нижнем окне автоматический интервьюер, в верхнем кандидат отвечает на вопросы.
На этом и завершим третью часть где мы, реализовав для нашего сервиса две полезные функции прохождения экзамена и проведения технического интервью, использовали ещё две полезные возможности, предоставляемые технологией gRPC:
- Передача данных в виде потока от клиентского приложения к сервису.
- Двунаправленная потоковая передача данных между "клиентом" и "сервером" в рамках одного HTTP/2 соединения.
Таким образом, к этому моменту мы успели познакомиться с основными возможностями gRPC.После перерыва, примерно через месяц-другой, я планирую продолжить данную серию. В планах рассмотреть пример создания простенького мобильного Flutter приложения для работы с сервисом Umka, "деплой" сервиса на реальный сервер, ... .До встречи в следующей части!
===========
Источник:
habr.com
===========
Похожие новости:
- [Dart, Профессиональная литература] Книга по Dart 2.12
- [Информационная безопасность, Обработка изображений, IT-стандарты, Математика] Формат JPEG XL будет полным по Тьюрингу без ограничения 1024*1024 пикселей
- [IT-стандарты, Карьера в IT-индустрии, IT-компании] Google закрыла программу поддержки молодых инженеров из-за жалоб на неравенство в жалованье
- [IT-стандарты, Карьера в IT-индустрии, Здоровье, IT-компании] Google завела внутренний инструмент для вычисления зарплаты переехавших удалёнщиков
- [Разработка мобильных приложений, Flutter] Внедрение зависимостей (Dependency Injection) с GetIt на примере Flutter-проекта
- [Тестирование IT-систем, IT-стандарты, Учебный процесс в IT, IT-компании] Управляемое тестирование: с чего мы начинаем, чтобы не было мучительно больно
- [Разработка веб-сайтов, JavaScript, IT-стандарты, VueJS, TypeScript] Vue 3: CompositionAPI + Typescript эксперименты
- [Dart, Flutter] gRPC + Dart, Сервис + Клиент, напишем? Часть 2
- [JavaScript, API, Стандарты связи] Консорциум Всемирной паутины принял стандарт Web Audio в качестве официального
- [Dart, Flutter] gRPC + Dart, Сервис + Клиент, напишем
Теги для поиска: #_dart, #_flutter, #_dart, #_flutter, #_grpc, #_bidirectional, #_stream, #_dart (дарт), #_flatter (флаттер), #_potokovaja_peredacha_dannyh (потоковая передача данных), #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:33
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, я Андрей, работаю Flutter разработчиком в компании Финам.Продолжим развивать сервис Umka. ЭкзаменНа примере реализации кода для проведения "экзамена" мы познакомимся с возможностью технологии gRPC передавать данные в виде потока от клиентского приложения на сервис.Сценарий экзамена пусть будет таким:
syntax="proto3";
message Student { int32 id = 1; string name = 2; } message Question { int32 id = 1; string text = 2; } message Answer { int32 id = 1; Student student = 2; Question question = 3; string text = 4; } message Evaluation { int32 id = 1; int32 answerId = 2; int32 mark = 3; } message AnsweredQuestion { Question question = 1; string answer = 2; } message Exam { int32 id = 1; repeated Question questions = 2; } service Umka { rpc getQuestion(Student) returns(Question) {} rpc sendAnswer(Answer) returns(Evaluation) {} rpc getTutorial(Student) returns (stream AnsweredQuestion) {} rpc getExam(Student) returns (Exam) {} rpc takeExam(stream Answer) returns(Evaluation) {} } @override
Future<Exam> getExam(ServiceCall call, Student request) async { final exam = Exam()..id = 1; exam.questions.addAll(questionsDb); return exam; } @override
Future<Evaluation> takeExam(ServiceCall call, Stream<Answer> asnswers) async { var score = 0; await for (var answer in asnswers) { final isCorrect = getCorrectAnswerById(answer.question.id) == answer.text; print('Received an answer from ${answer.student.name}\n' 'for a question: ${answer.question.text}' 'answer: ${answer.text} is correct: $isCorrect'); if (isCorrect) { score++; } } print('The student: ${call.clientMetadata?['student_name']}' ' finished exam with the score: $score'); return Evaluation() ..id = 1 ..mark = score; } }
Future<Evaluation> takeExam(Student student) async {
final exam = await stub.getExam(student); final questions = exam.questions; final answersStream = StreamController<Answer>(); final evaluationFuture = stub.takeExam(answersStream.stream, options: CallOptions(metadata: {'student_name': '${student.name}'})); for (var question in questions) { final answer = Answer() ..question = question ..student = student; print('Enter the answer for the question: ${question.text}'); answer.text = stdin.readLineSync()!; answersStream.add(answer); await Future.delayed(Duration(milliseconds: 1)); } unawaited(answersStream.close()); return evaluationFuture; }
Выполним команду dart pub get.Метод обращения к сервису напишем так: Future<void> callService(Student student) async {
final evaluation = await takeExam(student); print('${student.name}, your exam score is: ${evaluation.mark}'); await channel.shutdown(); }
Запуск экзаменаВ разных терминальных окнах стартуем сервис и клиентское приложение
"Студент" Ваня опечатался при ответе на третий вопрос и получил "четвёрку".Техническое интервьюМы подошли к самому интересному. Парой-тройкой десятков строчек кода мы реализуем чат, для проведения технического интервью.Для этого используем возможность gRPC осуществлять двунаправленную потоковую передачу данных от сервиса к клиентскому приложению и обратно в рамках одного HTTP/2 соединения.В описание сервиса добавим тип: message InterviewMessage {
string name = 1; string body = 2; } rpc techInterview(stream InterviewMessage) returns(stream InterviewMessage) {}
const interviewQuestions = [
'What was wrong in your previous job place?', 'Why do you want to work for Us?', 'Who do you see yourself in 5 years?', 'We will inform you about the decision. Bye!', ]; InterviewMessage _createMessage(String text, {String name = 'Interviewer'}) =>
InterviewMessage() ..name = name ..body = text; @override
Stream<InterviewMessage> techInterview( ServiceCall call, Stream<InterviewMessage> interviewStream) async* { var count = 0; await for (var message in interviewStream) { print('Candidate ${message.name} message: ${message.body}'); if (count >= interviewQuestions.length) { return; } else { yield _createMessage(interviewQuestions[count++]); } } }
Код клиентского приложения"На клиенте" код метода techInterview пусть будет такой: Future<void> techInterview(String candidateName) async {
final candidateStream = StreamController<InterviewMessage>(); final interviewerStream = stub.techInterview(candidateStream.stream); candidateStream.add(InterviewMessage() ..name = candidateName ..body = 'I am ready!'); await for (var message in interviewerStream) { print('\nMessage from the ${message.name}:\n${message.body}\n'); print('Enter your answer:'); final answer = stdin.readLineSync(); candidateStream.add(InterviewMessage()..body = answer!); } unawaited(candidateStream.close()); }
Future<void> callService(Student student) async {
await techInterview(student.name); await channel.shutdown(); } Вот гифка демонстрирующая процесс "интервью". В нижнем окне автоматический интервьюер, в верхнем кандидат отвечает на вопросы. На этом и завершим третью часть где мы, реализовав для нашего сервиса две полезные функции прохождения экзамена и проведения технического интервью, использовали ещё две полезные возможности, предоставляемые технологией gRPC:
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:33
Часовой пояс: UTC + 5