เมื่อพัฒนาแอปพลิเคชัน 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 เลือกแนวทางที่ดีที่สุดสำหรับแอปของคุณตามความต้องการเฉพาะ และสร้างแอปพลิเคชันที่มีประสิทธิภาพ ปรับขนาดได้ และบำรุงรักษาได้