เมื่อพัฒนาแอปพลิเคชัน Flutter คุณอาจต้องการรักษาต้นทุนการพัฒนาให้ต่ำที่สุด ทำให้โซลูชันรวดเร็ว เชื่อถือได้ และปลอดภัย ในคู่มือนี้ (หรือภาพรวมทางเทคนิคพร้อมความคิดเห็น) ฉันจะอธิบายวิธีเชื่อมต่อแอปพลิเคชัน Flutter ของคุณกับแบ็กเอนด์โดยใช้ HTTP REST API และ gRPC คุณจะได้เรียนรู้วิธีเลือกแบ็กเอนด์ที่เหมาะสมสำหรับแอปพลิเคชันของคุณ วิธีตั้งค่า และวิธีเชื่อมต่อแอปพลิเคชันของคุณเข้ากับแอปพลิเคชัน

จะเลือกอะไรดี?

ไม่ใช่ความลับที่ REST API เป็นวิธีการสื่อสารที่ใช้กันทั่วไประหว่างฟรอนต์เอนด์และแบ็กเอนด์ในเว็บแอปพลิเคชันสมัยใหม่และโครงสร้างพื้นฐานที่ใช้ไมโครเซอร์วิส อย่างไรก็ตาม เป็นที่น่าสังเกตว่าไมโครเซอร์วิสสามารถใช้วิธีการสื่อสารอื่นได้เช่นกัน

HTTP REST API ใช้โปรโตคอล HTTP เพื่อถ่ายโอนข้อมูลระหว่างไคลเอนต์และเซิร์ฟเวอร์ มันใช้งานง่ายและเข้าใจง่ายสำหรับนักพัฒนาส่วนใหญ่ หากต้องการใช้งาน คุณต้องกำหนดคำขอที่แอปพลิเคชันของคุณควรส่งไปยังเซิร์ฟเวอร์ รวมถึงโครงสร้างของการตอบกลับที่คุณคาดว่าจะได้รับ บทความนี้แสดงตัวอย่างการใช้แพ็คเกจ Dio

gRPC (Google Remote Procedure Call) เป็นแนวทางใหม่ในการสื่อสารระหว่างไคลเอนต์และเซิร์ฟเวอร์ โดยอิงตามสถาปัตยกรรม RPC แบบโอเพ่นซอร์ส ใช้รูปแบบการแลกเปลี่ยนข้อความ Protobuf ซึ่งเป็นรูปแบบที่มีประสิทธิภาพสูงสำหรับการแลกเปลี่ยนข้อความที่มีการบรรจุในระดับสูง (ด้วยการบังคับใช้คุณสมบัติ http/2) สำหรับการจัดลำดับข้อมูลที่มีโครงสร้าง ในบางกรณี การใช้ gRPC API อาจมีประสิทธิภาพมากกว่า REST API

เมื่อพัฒนาแอปพลิเคชัน ส่วนหน้าและส่วนหลังมักจะถูกสร้างขึ้นโดยบุคคลที่มีความสามารถต่างกัน เครื่องมือต่างๆ เช่น ผยองและทำซ้ำ ใช้เพื่อให้คำแนะนำการใช้งานสำหรับ REST ในกรณีของ gRPC ส่วนหน้าจะได้รับโค้ดที่เกือบจะพร้อมใช้งาน นอกจากนี้ ยังควรพิจารณาข้อเท็จจริงที่ว่าหากโครงการเกี่ยวข้องกับเว็บแอปพลิเคชัน การใช้ gRPC สำหรับแอปพลิเคชันมือถืออาจมีราคาแพงเกินไป

กำลังเชื่อมต่อกับ HTTP REST

หากต้องการทำงานกับ REST API ฉันขอแนะนำให้ใช้แพ็คเกจ Dio เนื่องจากมีประโยชน์ใช้สอยและสะดวกกว่าแพ็คเกจ http มาตรฐาน หากคุณสร้างเว็บแอปพลิเคชันขึ้นมา ก็จะคล้ายกับ axios

ในการใช้ Dio คุณต้องสร้างคลาสสำหรับการทำงานกับการเชื่อมต่อเครือข่าย ซึ่งคุณจะกำหนดคำขอที่แอปพลิเคชันของคุณควรส่งไปยังเซิร์ฟเวอร์ รวมถึงโครงสร้างของการตอบกลับที่คุณคาดหวังจะได้รับ

“ กระพือผับเพิ่ม dio ”

เพิ่มแพ็คเกจ dio ให้กับโปรเจ็กต์ของคุณ

มาสร้างคลาสสำหรับการทำงานกับการเชื่อมต่อเครือข่ายกัน:

import 'package:dio/dio.dart';

class NetworkService {
  late final Dio _dio;
  final JsonEncoder _encoder = const JsonEncoder();
  static final NetworkService _instance = NetworkService.internal();

  NetworkService.internal();

  static NetworkService get instance => _instance;

  Future<void> initClient() async {
    _dio = Dio(
      BaseOptions(
        baseUrl: Constant.baseUrl,
        connectTimeout: 60000,
        receiveTimeout: 6000,
      ),
    );
// A place for interceptors. For example, for authentication and logging
  }

  Future<dynamic> get(
    String url, {
    Map<String, dynamic>? queryParameters,
  }) async {
    try {
      final response = await _dio.get(url, queryParameters: queryParameters);
      return response.data;
    } on DioError catch (e) {
      final data = Map<String, dynamic>.from(e.response?.data);
      throw Exception(data['message'] ?? "Error while fetching data");
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> download(String url, String path) async {
    return _dio.download(url, path).then((Response response) {
      if (response.statusCode! < 200 || response.statusCode! > 400) {
        throw Exception("Error while fetching data");
      }
      return response.data;
    }).onError((error, stackTrace) {
      throw Exception(error);
    });
  }

  Future<dynamic> delete(String url) async {
    return _dio.delete(url).then((Response response) {
      if (response.statusCode! < 200 || response.statusCode! > 400) {
        throw Exception("Error while fetching data");
      }
      return response.data;
    }).onError((DioError error, stackTrace) {
      _log(error.response);
    });
  }

  Future<dynamic> post(String url, {body, encoding}) async {
    try {
      final response = await _dio.post(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> postFormData(String url, {required FormData data}) async {
    try {
      final response = await _dio.post(url, data: data);
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> patch(String url, {body, encoding}) async {
    try {
      final response = await _dio.patch(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw Exception(e.response?.data['detail'] ?? e.toString());
    } catch (e) {
      rethrow;
    }
  }

  Future<dynamic> put(String url, {body, encoding}) async {
    try {
      final response = await _dio.put(url, data: _encoder.convert(body));
      return response.data;
    } on DioError catch (e) {
      throw e.toString();
    } catch (e) {
      rethrow;
    }
  }
}

ตัวอย่าง

final NetworkService _client;

Future<String> login(LoginRequest loginRequest) async {
  try {
    final jsonData = await _client.post(
      "${Constant.baseUrl}/v1/auth/login",
      body: loginRequest.toJson()
    );
    return jsonData['access_token']
  } catch (e) {
    rethrow;
  }
}

กำลังเชื่อมต่อกับ gRPC

หากต้องการทำงานกับ gRPC คุณต้องใช้โค้ดที่สร้างขึ้นตามไฟล์โปรโต สร้างบริการสำหรับงานซึ่งจะใช้คลาส HelloClient ที่สร้างโดยใช้ gRPC เมื่อคุณสร้างอินสแตนซ์ของ HelloClient คุณจะต้องผ่านช่องทางที่จะใช้ในการส่งคำขอ

ตอนนี้รวมไคลเอนต์ gRPC ที่สร้างขึ้นเข้ากับแอปพลิเคชัน Flutter ของคุณ:

“ กระพือผับเพิ่ม grpc ”

เพิ่มแพ็คเกจ grpc ในโครงการของคุณ

การสร้างบริการสำหรับงาน:

import 'package:grpc/grpc.dart';
//import your autogenerate code
import '../services/proto/hello.pbgrpc.dart';

class HelloService {

  ///here enter your host without the http part
  String baseUrl = "example.com";

  HelloService._internal();
  static final HelloService _instance = HelloService._internal();

  factory HelloService() => _instance;

  ///static HelloService instance that we will call when we want to make requests
  static HelloService get instance => _instance;
   ///HelloClient is the  class that was generated for us when we ran the generation command
  ///We will pass a channel to it to intialize it
  late HelloClient _helloClient;

  ///this will be used to create a channel once we create this class.
  ///Call HelloService().init() before making any call.
  Future<void> init() async {
    final channel = ClientChannel(
      baseUrl,
      port: 443,
      options: const ChannelOptions(),
    );
    _helloClient = HelloClient(channel);
  }

  ///provide public access to the HelloClient instance
  HelloClient get helloClient {
    return _helloClient;
  }
}

ในฟังก์ชั่นหลักของคุณให้เริ่มต้นคลาส HelloService ดังด้านล่าง

HelloService().init();

โทร gRPC

Future<void> sayHello() async {
  try {
    HelloRequest helloRequest = HelloRequest();
    helloRequest.name = "Itachi";
    var res = await HelloService.instance.helloClient.sayHello(helloRequest);
  } catch (e) {
    print(e);
  }
}

การเปรียบเทียบ REST และ gRPC สำหรับแอปพลิเคชัน Flutter

  • ข้อดีของ REST: เรียบง่าย เข้าใจง่าย รองรับอย่างกว้างขวาง สามารถแคชได้ และเหมาะสำหรับแอปพลิเคชันส่วนใหญ่
  • ข้อเสียของ REST: เวลาแฝงที่สูงขึ้น, การทำให้ซีเรียลไลซ์มีประสิทธิภาพน้อยกว่า, ความสามารถในการสตรีมที่จำกัด
  • ข้อดีของ gRPC: เวลาแฝงต่ำ, การทำให้เป็นอนุกรมที่มีประสิทธิภาพ, รองรับการสตรีม, ไลบรารีไคลเอนต์ที่แข็งแกร่ง และเหมาะสำหรับแอปพลิเคชันแบบเรียลไทม์และไมโครเซอร์วิส
  • ข้อเสียของ gRPC: มีเส้นโค้งการเรียนรู้ที่สูงชัน ไม่สามารถอ่านได้ของมนุษย์ รองรับเว็บเบราว์เซอร์ได้น้อย และอาจใช้งานมากเกินไปสำหรับแอปพลิเคชันทั่วไป

เมื่อเลือกระหว่าง REST และ gRPC สำหรับแอปพลิเคชัน Flutter ของคุณ ให้พิจารณาความต้องการและข้อกำหนดเฉพาะของแอปของคุณ REST เป็นตัวเลือกที่ดีสำหรับแอปพลิเคชันส่วนใหญ่ ในขณะที่ gRPC เหมาะอย่างยิ่งสำหรับแอปพลิเคชันแบบเรียลไทม์หรือที่มีรูปแบบการสื่อสารที่ซับซ้อนระหว่างบริการ

ในบทความนี้ เราได้สำรวจวิธีจัดระเบียบแอปพลิเคชัน Flutter ด้วยแบ็กเอนด์ API โดยมุ่งเน้นไปที่สองแนวทางหลัก: HTTP REST และ gRPC เราได้พูดคุยถึงการออกแบบ API การใช้งานไคลเอ็นต์ใน Flutter

ด้วยการทำความเข้าใจข้อดีและข้อเสียของแต่ละแนวทาง คุณสามารถตัดสินใจอย่างมีข้อมูลเมื่อออกแบบแอปพลิเคชัน Flutter ด้วยแบ็กเอนด์ API เลือกแนวทางที่ดีที่สุดสำหรับแอปของคุณตามความต้องการเฉพาะ และสร้างแอปพลิเคชันที่มีประสิทธิภาพ ปรับขนาดได้ และบำรุงรักษาได้