Skip to content

Commit

Permalink
add review
Browse files Browse the repository at this point in the history
===

chore(platform): drop deprecated API and backward compatibility

To prepare for `1.0.0` major release, deprecated API has been removed.
- support for legacy heartbeat liveness, request-response subscription and intent subscription protocols
- legacy notation for optional and required parameters
- checks for optional wildcard characters in capability and intention qualifiers

closes #196

BREAKING CHANGE: dropping deprecated API and backward compatibility introduced a breaking change for host and client applications.

To migrate:
- Update host and clients to version `1.0.0-rc.13` or higher.
- Deprecated property `ManifestService.lookupApplications$` has been removed; use `ManifestService.applications` instead.
- Deprecated property `Capability.requiredParams` has been removed; declare required parameters via `Capability.params` instead and mark it as required.
- Deprecated property `Capability.optionalParams` has been removed; declare optional parameters via `Capability.params` instead and mark it as non-required.
  • Loading branch information
danielwiehl committed May 12, 2023
1 parent 677e25c commit b01b99e
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 28 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ To migrate:
<summary><strong>Deprecation Policy</strong></summary>
<br>

You can deprecate an API in any version. Backward compatibility is mandatory for at least one minor release. Deprecated APIs are only removed in a major release.
You can deprecate an API in any version. Deprecated APIs are only removed in a major release.

When deprecating API, mark it with the `@deprecated` JSDoc comment tag and include the current library version. Optionally, you can also specify which API to use instead, as following:

Expand Down
6 changes: 6 additions & 0 deletions apps/microfrontend-platform-devtools/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import {Beans} from '@scion/toolkit/bean-manager';
import {A11yModule} from '@angular/cdk/a11y';
import {AppNamePipe} from './app-name.pipe';
import {CustomParamMetadataPipe} from './custom-param-metadata.pipe';
import {ParamsFilterPipe} from './params-filter.pipe';
import {NullIfEmptyPipe} from './null-if-empty.pipe';
import {JoinPipe} from './join.pipe';

@NgModule({
declarations: [
Expand All @@ -64,6 +67,9 @@ import {CustomParamMetadataPipe} from './custom-param-metadata.pipe';
QualifierChipListComponent,
AppNamePipe,
CustomParamMetadataPipe,
ParamsFilterPipe,
NullIfEmptyPipe,
JoinPipe,
],
imports: [
BrowserModule,
Expand Down
25 changes: 25 additions & 0 deletions apps/microfrontend-platform-devtools/src/app/join.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2018-2023 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

import {Pipe, PipeTransform} from '@angular/core';

/**
* Joins given items by given separator, applying given project function if specified.
*/
@Pipe({name: 'devtoolsJoin'})
export class JoinPipe implements PipeTransform {

public transform<T>(items: T[] | null | undefined, separator: string, projectFn?: (value: T) => string): string {
if (!items?.length) {
return '';
}
return items.map(projectFn ?? (item => `${item}`)).join(separator);
}
}
40 changes: 40 additions & 0 deletions apps/microfrontend-platform-devtools/src/app/null-if-empty.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018-2023 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

import {Pipe, PipeTransform} from '@angular/core';

/**
* Returns `null` if the given object is empty.
*/
@Pipe({name: 'devtoolsNullIfEmpty'})
export class NullIfEmptyPipe implements PipeTransform {

public transform<T>(value: T): T {
if (value === null || value === undefined) {
return null;
}
else if (value instanceof Map || value instanceof Set) {
if (!value.size) {
return null;
}
}
else if (Array.isArray(value) || typeof value === 'string') {
if (!value.length) {
return null;
}
}
else if (typeof value === 'object') {
if (!Object.keys(value).length) {
return null;
}
}
return value;
}
}
23 changes: 23 additions & 0 deletions apps/microfrontend-platform-devtools/src/app/params-filter.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2018-2023 Swiss Federal Railways
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

import {Pipe, PipeTransform} from '@angular/core';
import {ParamDefinition} from '@scion/microfrontend-platform';

/**
* Filters required or optional parameters.
*/
@Pipe({name: 'devtoolsParamsFilter'})
export class ParamsFilterPipe implements PipeTransform {

public transform(params: ParamDefinition[] | undefined | null, filter?: 'required' | 'optional'): ParamDefinition[] {
return params?.filter(param => !filter || (filter === 'required' ? param.required : !param.required)) ?? [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@
<span>{{selectedCapability.description}}</span>
</section>

<section class="properties" *ngIf="selectedCapability.properties">
<section class="required-params" *ngIf="selectedCapability.params | devtoolsParamsFilter:'required' | devtoolsNullIfEmpty as requiredParams ">
<header>Required Params:</header>
{{requiredParams | devtoolsJoin:', ':paramNameFn}}
</section>

<section class="optional-params" *ngIf="selectedCapability.params | devtoolsParamsFilter:'optional' | devtoolsNullIfEmpty as optionalParams">
<header>Optional Params:</header>
{{optionalParams | devtoolsJoin:', ':paramNameFn}}
</section>

<section class="properties" *ngIf="selectedCapability.properties | devtoolsNullIfEmpty">
<header>Properties:</header>
<sci-property [properties]="selectedCapability.properties"></sci-property>
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* SPDX-License-Identifier: EPL-2.0
*/
import {ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges} from '@angular/core';
import {Capability} from '@scion/microfrontend-platform';
import {Capability, ParamDefinition} from '@scion/microfrontend-platform';
import {Router} from '@angular/router';
import {Observable, ReplaySubject} from 'rxjs';
import {expand, map, switchMap, take} from 'rxjs/operators';
Expand Down Expand Up @@ -76,4 +76,6 @@ export class RequiredCapabilitiesComponent implements OnChanges {
public trackByCapabilityFn(index: number, capability: Capability): string {
return capability.metadata.id;
}

public paramNameFn = (param: ParamDefinition): string => param.name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ include::{basedir}/_common.adoc[]

[[chapter:compatibility-and-deprecation-policy]]
== Compatibility and Deprecation Policy
We are aware that you need stability from the SCION Microfrontend Platform, primarily because microfrontends with potential different lifecycles are involved. Therefore, you can expect a decent release cycle of one or two major releases per year with strict https://semver.org/[semantic versioning] policy. Changes to the communication protocol between the host and micro applications are backward compatible for at least one minor release. This allows for the host and its clients to be updated independently.

Deprecation of APIs can occur in any release. Backward compatibility is mandatory for at least one minor release. Deprecated APIs are only removed in a major release.
We are aware that you need stability from the SCION Microfrontend Platform, primarily because microfrontends with potential different lifecycles are involved. Therefore, you can expect a decent release cycle of one or two major releases per year with strict https://semver.org/[semantic versioning] policy. Changes to the communication protocol between the host and micro applications are backward compatible in the same major release. This allows for the host and its clients to update independently.

Deprecation of APIs can occur in any release. Deprecated APIs are only removed in a major release.
4 changes: 3 additions & 1 deletion docs/site/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ Patch versions fix bugs or optimize existing features without breaking changes.
<img src="/docs/adoc/microfrontend-platform-developer-guide/images/semver.svg" alt="Versioning">
</p>

We are aware that you need stability from the SCION Microfrontend Platform, primarily because microfrontends with potential different lifecycles are involved. Therefore, you can expect a decent release cycle of one or two major releases per year. Changes in the communication protocol between the host and micro applications are backward compatible with the previous major version. When deprecating API, which can occur in any release, it will still be present in the next major release. Removal of deprecated API will occur only in a major release.
We are aware that you need stability from the SCION Microfrontend Platform, primarily because microfrontends with potential different lifecycles are involved. Therefore, you can expect a decent release cycle of one or two major releases per year with strict [semantic versioning](https://semver.org) policy. Changes to the communication protocol between the host and micro applications are backward compatible in the same major release. This allows for the host and its clients to update independently.

Deprecation of APIs can occur in any release. Deprecated APIs are only removed in a major release.

[menu-home]: /README.md
[menu-projects-overview]: /docs/site/projects-overview.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const headersExtractFn = <T>(msg: TopicMessage<T> | IntentMessage<T>): Map<strin
const paramsExtractFn = <T>(msg: IntentMessage<T>): Map<string, any> => msg.intent.params;
const capabilityIdExtractFn = <T>(msg: IntentMessage<T>): string => msg.capability.metadata.id;

describe('Messaging', () => {
fdescribe('Messaging', () => {

const disposables = new Set<Disposable>();

Expand Down Expand Up @@ -1272,7 +1272,7 @@ describe('Messaging', () => {
capabilities: [
{
type: 'capability',
params: [{name: 'param1', required: true}, {name: 'param2', required: true}],
params: [{name: 'param1', required: true}],
},
],
},
Expand All @@ -1283,8 +1283,8 @@ describe('Messaging', () => {
// publish
const observeCaptor = new ObserveCaptor(paramsExtractFn);
Beans.get(IntentClient).observe$<string>({type: 'capability'}).subscribe(observeCaptor);
await Beans.get(IntentClient).publish({type: 'capability', params: new Map().set('param1', 'value1').set('param2', 'value2')});
await expectEmissions(observeCaptor).toEqual(new Map().set('param1', 'value1').set('param2', 'value2'));
await Beans.get(IntentClient).publish({type: 'capability', params: new Map().set('param1', 'value1')});
await expectEmissions(observeCaptor).toEqual(new Map().set('param1', 'value1'));
});

it('should preserve data type of passed intent parameters', async () => {
Expand Down Expand Up @@ -1403,7 +1403,7 @@ describe('Messaging', () => {
capabilities: [
{
type: 'capability',
params: [{name: 'param1', required: false}, {name: 'param2', required: false}],
params: [{name: 'param1', required: false}],
},
],
},
Expand All @@ -1426,7 +1426,7 @@ describe('Messaging', () => {
capabilities: [
{
type: 'capability',
params: [{name: 'param1', required: true}, {name: 'param2', required: true}],
params: [{name: 'param1', required: true}],
},
],
},
Expand All @@ -1435,9 +1435,8 @@ describe('Messaging', () => {
});

// publish
await expectPromise(Beans.get(IntentClient).publish({type: 'capability', params: new Map().set('param1', 'value1')})).toReject(/\[IntentParamValidationError].*missingParams=\[param2]/);
await expectPromise(Beans.get(IntentClient).publish({type: 'capability', params: new Map().set('param2', 'value2')})).toReject(/\[IntentParamValidationError].*missingParams=\[param1]/);
await expectPromise(Beans.get(IntentClient).publish({type: 'capability', params: new Map().set('param1', 'value1').set('param2', 'value2')})).toResolve();
await expectPromise(Beans.get(IntentClient).publish({type: 'capability'})).toReject(/\[IntentParamValidationError].*missingParams=\[param1]/);
await expectPromise(Beans.get(IntentClient).publish({type: 'capability', params: new Map().set('param1', 'value1')})).toResolve();
});

it('should reject an intent if it includes non-specified parameter', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,13 @@ export class ɵManifestRegistry implements ManifestRegistry, PreDestroy {
throw illegalQualifierError;
}

assertCapabilityParamDefinitions(capability.params ?? []);

// Let the host app intercept the capability to register.
const capabilityToRegister = await interceptCapability({
...capability,
qualifier: capability.qualifier ?? {},
params: coerceCapabilityParamDefinitions(capability),
params: capability.params ?? [],
private: capability.private ?? true,
metadata: {
id: UUID.randomUUID(),
Expand Down Expand Up @@ -281,17 +283,6 @@ function assertIntentionRegisterApiEnabled(appSymbolicName: string): void {
}
}

function coerceCapabilityParamDefinitions(capability: Capability): ParamDefinition[] {
const params: ParamDefinition[] = [];

capability.params?.forEach(param => {
params.push(param);
});

assertCapabilityParamDefinitions(params);
return params;
}

/**
* Asserts given parameter definitions to be valid.
*/
Expand Down

0 comments on commit b01b99e

Please sign in to comment.