الزاوي: اجعل الشفرة قابلة للقراءة للجزء الخلفي. المكافأة: تبديل واجهات برمجة التطبيقات والتخزين المؤقت للاستعلام

في كثير من الأحيان في المشروع ، فإن وتيرة تطوير الواجهة الأمامية تسبق وتيرة تطوير الواجهة الخلفية. في هذه الحالة ، ينشأ شيئان:

  1. القدرة على إطلاق الجبهة بدون خلفية ، أو بدون نقاط نهاية منفصلة ؛
  2. صف للجهة الخلفية نقاط النهاية المطلوبة وشكل الطلب والاستجابة وما إلى ذلك.

أرغب في مشاركة طريقي لتنظيم التعليمات البرمجية المسؤولة عن طلبات واجهة برمجة التطبيقات ، والتي تحل هاتين المشكلتين بشكل مثالي ، كما تتيح لك تخزين الطلبات مؤقتًا.

قم بإنشاء ملف التكوين api.config.json في جذر المشروع:

{
  "apiUrl": "https://api.example.com",
  "useFakeApiByDefault": false,
  "fakeEndPoints": ["Sample"]
}

نعين هنا عنوان URL الأساسي لواجهة برمجة التطبيقات ، إذا كانت المعلمة useFakeApiByDefault = true ، فإن تطبيقنا سيستخدم فقط بذرة بدلاً من جميع الطلبات. إذا كان خطأ ، فسيتم استخدام بذرة الطلبات فقط من الطلبات من صفيف fakeEndPoints.

لاستيراد JSON إلى الشفرة ، نضيف سطرين إلى قسم CompilerOptions من ملف tsconfig.json:

    "resolveJsonModule": true,
    "esModuleInterop": true,

إنشاء BaseEndpoint الفئة الأساسية.

/src/app/api/_base.endpoint.ts:

import {ApiMethod, ApiService} from '../services/api/api.service';
import * as ApiConfig from '../../../api.config.json';
import {ErrorService} from '../services/error/error.service';
import {NgModule} from '@angular/core';

@NgModule({providers: [ApiService, ErrorService]})

export abstract class BaseEndpoint<Request, ResponseModel> {
  protected url: string;
  protected name: string;
  protected method: ApiMethod = ApiMethod.Post;
  protected sampleResponse: ResponseModel;
  protected cache: boolean = false;
  protected responseCache: ResponseModel = null;

  constructor(private api: ApiService, private error: ErrorService) {
  }

  public execute(request: Request): Promise<ResponseModel> {
    if (this.cache && this.responseCache !== null) {
      return new Promise<ResponseModel>((resolve, reject) => {
        resolve(this.responseCache);
      });
    } else {
      if (ApiConfig.useFakeApiByDefault || ApiConfig.fakeEndPoints.includes(this.name)) {
        console.log('Fake Api Request:: ', this.name, request);
        console.log('Fake Api Response:: ', this.sampleResponse);
        return new Promise<ResponseModel>((resolve) => resolve(this.sampleResponse));
      } else {
        return new Promise<ResponseModel>((resolve, reject) => {
          this.api.execute(this.url, this.method, request).subscribe(
            (response: Response<ResponseModel>) => {
              if (response.status === 200) {
                if (this.cache) { this.responseCache = response.payload; }
                resolve(this.payloadMap(response.payload));
              } else {
                this.error.emit(response.error);
                reject(response.error);
              }
            }, response => {
              this.error.emit('    ')
              reject('    '));
           }
        });
      }
    }
  }

  protected payloadMap(payload: ResponseModel): ResponseModel { return payload; }
}

abstract class Response<T> {
  public status: number;
  public error: string;
  public payload: T;
}

<Request، ResponseModel> - أنواع حالات الطلب والاستجابة. ردا على ذلك ، تم بالفعل سحب الحمولة.

لن أنشر فئتي ApiService و ErrorService ، حتى لا تضخم المنشور ، لا يوجد شيء خاص هناك. يرسل ApiService طلبات http ، ويسمح لك ErrorService بالاشتراك في الأخطاء. لعرض الأخطاء ، تحتاج إلى الاشتراك في ErrorService في المكون حيث نريد بالفعل عرض الأخطاء (أقوم بالتوقيع على التخطيط الرئيسي الذي أقوم فيه بعمل مشروط أو تلميح).

يبدأ السحر عندما نرث من هذه الفئة. سيبدو الفصل

المشتق كما يلي: /src/app/api/get-users.endpoint.ts

import {BaseEndpoint} from './_base.endpoint';
import {Injectable} from '@angular/core';
import {UserModel} from '../models/user.model';

@Injectable()
export class GetUsersEndpoint extends BaseEndpoint<GetUsersRequest, UserModel[]> {
  protected name = 'GetUsers';
  protected url= '/getUsers';
  protected sampleResponse = [{
      id: 0,
      name: 'Ivan',
      age: 18
  },
  {
      id: 1,
      name: 'Igor',
      age: 25
   }

  protected payloadMap(payload) {
    return payload.map(user => new UserModel(user));
  }
}

export class GetUsersRequest {
   page: number;
   limit: number;
}

لا شيء أكثر ، من محتويات الملف ، يتضح على الفور عنوان URL ، وما تنسيق الطلب (GetUserRequest) ، وما تنسيق الاستجابة.

إذا كانت هذه الملفات موجودة في مجلد / واجهة برمجة تطبيقات منفصلة ، فإن كل ملف يتوافق مع نقطة النهاية الخاصة به ، أعتقد أنه يمكنك إظهار هذا المجلد في النهاية الخلفية ، ونظريًا ، إذا كنت كسولًا جدًا في كتابة الوثائق على واجهة برمجة التطبيقات ، يمكنك الاستغناء عنها. لا يزال بإمكانك تشتيت الملفات داخل المجلدات / واجهة برمجة التطبيقات في المجلدات وفقًا لوحدات التحكم.

إذا قمت بإضافة "GetUsers" إلى مصفوفة التكوين "fakeEndPoints" ، فلن يكون هناك طلب ، وسيتم استبدال الاستجابة ببيانات كعب من sampleResponse.

من أجل تصحيح الطلبات المزيفة (في علامة التبويب "الشبكة" ، بالطبع لن نرى أي شيء) ، لقد قدمت إخراج وحدة التحكم لإخراج سطرين إلى فئة وحدة التحكم:

console.log('Fake Api Request:: ', this.name, request);
console.log('Fake Api Response:: ', this.sampleResponse);

إذا تجاوزت ذاكرة التخزين المؤقت لخاصية الفئة = true ، فسيتم تخزين الطلب في ذاكرة التخزين المؤقت (في المرة الأولى التي يتم فيها تقديم طلب إلى واجهة برمجة التطبيقات ، ثم يتم دائمًا إرجاع نتيجة الطلب الأول). الحقيقة هنا هي وضع اللمسات الأخيرة: لا تحتاج إلى عمل التخزين المؤقت إلا إذا كانت معلمات الطلب (محتويات مثيل من فئة UserRequest).

نعيد تعريف طريقة payloadMap إذا كنا بحاجة إلى أي تحويلات للبيانات المستلمة من الخادم. إذا لم يتم إعادة تعريف الطريقة ، فستكون البيانات من الحمولة في الوعد الذي تم إرجاعه.

الآن نحصل على البيانات من API في المكون:

import {Component, OnInit, ViewChild} from '@angular/core';
import {UserModel} from '../../models/user.model';
import {GetUsersEndpoint, GetUsersRequest} from '../../api/get_users.endpoint';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.css'],
  providers: [GetUsersEndpoint]
})
export class UsersComponent implements OnInit {

  public users: UserModel[];
  public request: GetUsersRequest = {page: 1, limit: 20};

  constructor(private getUsersEndpoint: GetUsersEndpoint) {    
  }

  private load() {
    this.getUsersEndpoint.execute(this.request).then(data => {
      this.users = data;
    });
  }

  ngOnInit(): void {
    this.load();
  }
}


في مثل هذا التنفيذ ، من الممكن أن تظهر للعميل نتيجة إكمال قائمة أمنياته ، حتى إذا لم تكتمل الخلفية الخلفية بعد لقائمة الرغبات هذه. ضع نقاط النهاية اللازمة على كعب الروتين - ويمكنك بالفعل "لمس" الميزات والحصول على تعليقات.

أتطلع في التعليقات إلى النقد البناء بشأن المشكلات التي قد تنشأ عند بناء الوظائف ، والتي يمكن أن تبرز المزالق الأخرى.

في المستقبل ، أريد إعادة كتابة هذا الحل من الوعود إلى التزامن / الانتظار. أعتقد أن الرمز سيكون أكثر أناقة.

في المخزون ، هناك العديد من الحلول المعمارية لـ Angular ، في المستقبل القريب أخطط للمشاركة.

All Articles