サービスコントロールパネル。 パート2.フロントエンドへの道

エントリー。 APIについてもう少し。



画像







そのため、前回 、APIアセンブリプロセスの説明に落ち着きました。それ以降、いくつかのことが変更されました。 すなわち-GruntはGulpに置き換えられました。 この再配置の主な理由は、作業の速度です。







移行後、違いは肉眼で顕著になりました(特に、gulpが各タスクに費やした時間を表示するため)。 この結果は、すべてのタスクがデフォルトで並行して実行されるという事実により達成されます。 そのようなソリューションは、私たちに完全に適合しました。 Gruntが行った作業の一部は互いに独立していたため、同時に実行できました。 たとえば、定義およびパスファイルのインデントを追加します。

残念ながら、いくつかの短所がありました。 前のタスクの実装を必要とするタスクは残りました。 しかし、 gulpにはあらゆる場合に対応する広範なパッケージデータベースがあるため、ソリューションはすぐに見つかりました-runSequenceパッケージ







gulp.task('client', (callback) => { runSequence( 'client:indent', 'client:concat', 'client:replace', 'client:validate', 'client:toJson', 'client:clean', callback ); });
      
      





つまり、gulpの標準タスク宣言の代わりに、コールバックが引数として渡され、指定された順序でタスクが実行されます。 私たちのケースでは、実行順序は40のタスクのうち4つのタスクでのみ重要だったため、Gruntと比較した速度の増加が顕著です。







また、gulpはES6を支持してcoffeescriptを放棄することを許可しました。 コードはほとんど変更されませんでしたが、apiアセンブリー構成の変更が頻繁に行われないコーヒースクリプトを記述する方法を覚えておく必要はありませんでした。

比較のための構成の一部の例:







ガルプ



 gulp.task('admin:indent_misc', () => { return gulp.src(`${root}/projects.yml`) .pipe(indent({ tabs: false, amount: 2 })) .pipe(gulp.dest(`${interimDir}`)) });
      
      





うなり声



 indent: admin_misc: src: [ '<%= admin_root %>/authorization.yml' '<%= admin_root %>/projects.yml' ] dest: '<%= admin_interim_dir %>/' options: style: 'space' size: 2 change: 1
      
      







また、私たちがなんとか踏み込んだ小さなレーキについて言及する価値があります。

それらは次のもので構成されていました:APIファイルを生成し、角度アプリケーションを開始しようとした後、ResourceServiceの再エクスポート中にエラーが発生しました。 検索の開始点は、api / model / models.tsファイルでした。 これには、将来使用されるすべてのインターフェースとサービスのエクスポートが含まれます。







ここで少し余談を加えて、swagger-codegenがインターフェースとサービスに名前を割り当てる方法を教えてください。







余談

インターフェース

インターフェイステンプレートに基づいてオブジェクトプロパティがオブジェクトタイプに設定されている場合、%EntityNamePropertyName%と呼ばれる別のインターフェイスが作成されます。







サービス

サービステンプレートに基づいて、サービス名はタグ名と単語サービスで構成されます(例:OrderService)。 したがって、仕様のパスに沿って複数のタグを指定すると、このメソッドは複数のサービスに分類されます。 このアプローチにより、ある場合には必要なサービスのみをインポートし、別のサービスにいくつかのサービスをインポートすることを回避できます。







そのため、models.tsファイルには、実際には2つのResourceServiceエクスポートがあり、1つはリソースエンティティのメソッドにアクセスするためのサービスを表し、もう1つはリソースエンティティのサービスプロパティのインターフェイスを表します。 したがって、そのような紛争が発生しました。 解決策は、プロパティの名前を変更することでした。







APIからフロントエンドまで。



画像







既に述べたように、swagger仕様では、バックエンドとフロントエンドの両方に必要なAPIファイルを作成できます。 私たちの場合、Angular2のAPIコード生成は、単純なコマンドを使用して行われます。







 java -jar ./swagger-codegen-cli.jar generate \ -i client_swagger.json \ -l typescript-angular \ -o ../src/app/api \ -c ./typescript_config.json
      
      





パラメーターの分析:









言語の数、ひいては生成用のテンプレートとコードが膨大であることを考えると、私たちのニーズに合わせてcodegenを再構築し、Typescript-Angularのみを残すというアイデアが定期的に頭に浮かびます。 さらに、開発者自身が独自のテンプレートを追加するための指示を提供します。







この簡単な方法で、APIを操作するために必要なすべてのモジュール、インターフェイス、クラス、およびサービスを取得します。

codegenを使用して取得したインターフェイスの1つの例:







Service_definition.yaml仕様の入力ファイル
 Service: type: object required: - id properties: id: type: integer description: Unique service identifier format: 'int32' readOnly: true date: type: string description: Registration date format: date readOnly: true start_date: type: string description: Start service date format: date readOnly: true expire_date: type: string description: End service date format: date readOnly: true status: type: string description: Service status enum: - 'empty' - 'allocated' - 'launched' - 'stalled' - 'stopped' - 'deallocated' is_primary: type: boolean description: Service primary state priority: type: integer description: Service priority format: 'int32' readOnly: true attributes: type: array description: Service attributes items: type: string primary_service: type: integer description: Unique service identifier format: 'int32' readOnly: true example: 138 options: type: array items: type: string order: type: integer description: Unique order identifier format: 'int32' readOnly: true proposal: type: integer description: Unique proposal identifier format: 'int32' readOnly: true resources: type: array items: type: object properties: url: type: string description: Resources for this service Services: type: array items: $ref: '#/definitions/Service'
      
      





出力は、角度が理解できるインターフェイスです。
 import { ServiceOptions } from './serviceOptions'; import { ServiceOrder } from './serviceOrder'; import { ServicePrimaryService } from './servicePrimaryService'; import { ServiceProposal } from './serviceProposal'; import { ServiceResources } from './serviceResources'; /** * Service entry reflects fact of obtaining some resources within order (technical part). In other hand service points to proposal that was used for ordering (commercial part). Service can be primary (ordered using tariff proposal) and non-primary (ordered using option proposal). */ export interface Service { /** * Record id */ id: number; /** * Service order date */ date?: string; /** * Service will only be launched after this date (if nonempty) */ start_date?: string; /** * Service will be stopped after this date (if nonempty) */ expire_date?: string; /** * Service current status. Meaning: * empty - initial status, not allocated * allocated - all option services and current service are allocated and ready to launch * launched - all option services and current one launched and works * stalled - service can be stalled in any time. Options also goes to the same status * stopped - service and option services terminates their activity but still stay allocated * deallocated - resources of service and option ones are released and service became piece of history */ status?: number; /** * Whether this service is primary in its order. Otherwise it is option service */ is_primary?: boolean; /** * Optional priority in order allocating process. The less number the earlier service will be allocated */ priority?: number; primary_service?: ServicePrimaryService; order?: ServiceOrder; proposal?: ServiceProposal; /** * Comment for service */ comment?: string; /** * Service's cost (see also pay_type, pay_period, onetime_cost) */ cost?: number; /** * Service's one time payment amount */ onetime_cost?: number; /** * Bill amount calculation type depending on service consuming */ pay_type?: Service.PayTypeEnum; /** * Service bill payment period */ pay_period?: Service.PayPeriodEnum; options?: ServiceOptions; resources?: ServiceResources; } export namespace Service { export enum PayTypeEnum { Fixed = <any> 'fixed', Proportional = <any> 'proportional' } export enum PayPeriodEnum { Daily = <any> 'daily', Monthly = <any> 'monthly', Halfyearly = <any> 'halfyearly', Yearly = <any> 'yearly' } }
      
      





service_path.yml仕様ファイルからの抜粋
 /dedic/services: get: tags: [Dedicated, Service] x-swagger-router-controller: app.controllers.service operationId: get_list security: - oauth: [] summary: Get services list parameters: - $ref: '#/parameters/limit' - $ref: '#/parameters/offset' responses: 200: description: Returns services schema: $ref: '#/definitions/Services' examples: application/json: objects: - id: 3 date: '2016-11-01' start_date: '2016-11-02' expire_date: '2017-11-01' status: 'allocated' is_primary: true priority: 3 primary_service: null options: url: "https://doc.miran.ru/api/v1/dedic/services/3/options" order: url: 'https://doc.miran.ru/api/v1/orders/3' comment: 'Test comment for service id3' cost: 2100.00 onetime_cost: 1000.00 pay_type: 'fixed' pay_period: 'daily' proposal: url: 'https://doc.miran.ru/api/v1/dedic/proposals/12' agreement: url: 'https://doc.miran.ru/api/v1/agreements/5' resorces: url: "https://doc.miran.ru/api/v1/dedic/services/3/resources" - id: 7 date: '2016-02-12' start_date: '2016-02-12' expire_date: '2016-02-12' status: 'stopped' is_primary: true priority: 2 primary_service: null options: url: "https://doc.miran.ru/api/v1/dedic/services/7/options" order: url: 'https://doc.miran.ru/api/v1/orders/7' comment: 'Test comment for service id 7' cost: 2100.00 onetime_cost: 1000.00 pay_type: 'fixed' pay_period: 'daily' proposal: url: 'https://doc.miran.ru/api/v1/dedic/proposals/12' agreement: url: 'https://doc.miran.ru/api/v1/agreements/2' resorces: url: "https://doc.miran.ru/api/v1/dedic/services/7/resources" total_count: 2 500: $ref: "#/responses/Standard500" post: tags: [Dedicated, Service] x-swagger-router-controller: app.controllers.service operationId: create security: - oauth: [] summary: Create service in order parameters: - name: app_controllers_service_create in: body schema: type: object additionalProperties: false required: - order - proposal properties: order: type: integer description: Service will be attached to this preliminary created order format: 'int32' minimum: 0 proposal: type: integer format: 'int32' description: Proposal to be used for service. Tariff will create primary service, not tariff - option one minimum: 0 responses: 201: description: Service successfully created 400: description: Incorrect order id (deleted or not found) or proposal id (expired or not found)
      
      





Angularの完成したサービスからの抜粋
 /* tslint:disable:no-unused-variable member-ordering */ import { Inject, Injectable, Optional } from '@angular/core'; import { Http, Headers, URLSearchParams } from '@angular/http'; import { RequestMethod, RequestOptions, RequestOptionsArgs } from '@angular/http'; import { Response, ResponseContentType } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import '../rxjs-operators'; import { AppControllersServiceCreate } from '../model/appControllersServiceCreate'; import { AppControllersServiceUpdate } from '../model/appControllersServiceUpdate'; import { InlineResponse2006 } from '../model/inlineResponse2006'; import { InlineResponse2007 } from '../model/inlineResponse2007'; import { InlineResponse2008 } from '../model/inlineResponse2008'; import { InlineResponse2009 } from '../model/inlineResponse2009'; import { InlineResponse401 } from '../model/inlineResponse401'; import { Service } from '../model/service'; import { Services } from '../model/services'; import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; import { Configuration } from '../configuration'; @Injectable() export class ServiceService { protected basePath = ''; public defaultHeaders: Headers = new Headers(); public configuration: Configuration = new Configuration(); constructor( protected http: Http, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) { if (basePath) { this.basePath = basePath; } if (configuration) { this.configuration = configuration; this.basePath = basePath || configuration.basePath || this.basePath; } } /** * * Extends object by coping non-existing properties. * @param objA object to be extended * @param objB source object */ private extendObj<T1,T2>(objA: T1, objB: T2) { for(let key in objB){ if(objB.hasOwnProperty(key)){ (objA as any)[key] = (objB as any)[key]; } } return <T1&T2>objA; } /** * @param consumes string[] mime-types * @return true: consumes contains 'multipart/form-data', false: otherwise */ private canConsumeForm(consumes: string[]): boolean { const form = 'multipart/form-data'; for (let consume of consumes) { if (form === consume) { return true; } } return false; } /** * * @summary Delete service * @param id Unique entity identifier */ public _delete(id: number, extraHttpRequestParams?: any): Observable<{}> { return this._deleteWithHttpInfo(id, extraHttpRequestParams) .map((response: Response) => { if (response.status === 204) { return undefined; } else { return response.json() || {}; } }); } /** * * @summary Create service in order * @param appControllersServiceCreate */ public create(appControllersServiceCreate?: AppControllersServiceCreate, extraHttpRequestParams?: any): Observable<{}> { return this.createWithHttpInfo(appControllersServiceCreate, extraHttpRequestParams) .map((response: Response) => { if (response.status === 204) { return undefined; } else { return response.json() || {}; } }); } /** * Create service in order * * @param appControllersServiceCreate */ public createWithHttpInfo appControllersServiceCreate?: AppControllersServiceCreate, extraHttpRequestParams?: any): Observable<Response> { const path = this.basePath + '/dedic/services'; let queryParameters = new URLSearchParams(); // https://github.com/angular/angular/issues/6845 let headers = new Headers(this.defaultHeaders.toJSON()); // to determine the Accept header let produces: string[] = [ 'application/json' ]; // authentication (oauth) required // oauth required if (this.configuration.accessToken) { let accessToken = typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken; headers.set('Authorization', 'Bearer ' + accessToken); } headers.set('Content-Type', 'application/json'); let requestOptions: RequestOptionsArgs = new RequestOptions({ method: RequestMethod.Post, headers: headers, // https://github.com/angular/angular/issues/10612 body: appControllersServiceCreate == null ? '' : JSON.stringify(appControllersServiceCreate), search: queryParameters, withCredentials:this.configuration.withCredentials }); // https://github.com/swagger-api/swagger-codegen/issues/4037 if (extraHttpRequestParams) { requestOptions = (<any>Object).assign(requestOptions, extraHttpRequestParams); } return this.http.request(path, requestOptions); } }
      
      





したがって、たとえば、対応するサービスを使用してサービスを作成する事後要求を行うには、次のことを行う必要があります。









パラメーターがJavaスタイルで命名されている理由をすぐに説明したいと思います。 理由は、この名前が仕様から、またはむしろ名前フィールドから形成されるからです:







 post: tags: [Dedicated, Service] x-swagger-router-controller: app.controllers.service operationId: create security: - oauth: [] summary: Create service in order parameters: - name: app_controllers_service_create in: body
      
      





名前が重複せず、一意になるように、このような扱いにくい名前を使用することにしました。 たとえば、データを名前として指定すると、codegenはカウンターをデータに追加します。これにより、Data_0、Data_1などの名前を持つ10個のインターフェイスが作成されます。 インポート時に適切なインターフェイスを見つけることが問題になります)。







また、codegenはインポートする必要があるモジュールを作成し、その名前はmethodタグに基づいて形成されることを知っておく価値があります。 したがって、上記のメソッドはDedicatedおよびServiceモジュールに存在します。 これは、API全体をインポートしてメソッド間を移動するのではなく、コンポーネントに必要なものだけを使用できるため便利です。







ご存知のように、Angular 4.4ではHttpModuleをHttpClientModuleに置き換えたため、利便性が向上しました(違いについてはこちらで確認できますが、残念ながら、現在の安定バージョンのcodegenはHttpModuleで動作します。







返されたHttpClientModuleはデフォルトでjsonでした:







 .map((response: Response) => { if (response.status === 204) { return undefined; } else { return response.json() || {}; }
      
      





承認用のヘッダーを追加するには、 HttpInterceptorを使用します。







 if (this.configuration.accessToken) { let accessToken = typeof this.configuration.accessToken === 'function' ? this.configuration.accessToken() : this.configuration.accessToken; headers.set('Authorization', 'Bearer ' + accessToken); }
      
      





私たちは更新を楽しみにしていますが、今のところ、私たちが持っているもので作業しています。







次の部分では、Angularについてのストーリーを直接開始し、APIはフロントエンドから既に触れます。








All Articles