Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(all): pass featureAppName as consumerName to Feature Service binder #589

Merged
merged 1 commit into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions docs/guides/custom-logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ defineFeatureAppLoader(featureAppManager, {logger});
To provide a custom logger to Feature Apps and Feature Services, the integrator
can provide the Logger Feature Service from the
[`@feature-hub/logger`][logger-api] package. It must be defined with a function
that takes the `consumerId` of the consuming Feature App or Feature Service, and
returns a [`Logger`][core-logger-interface] instance. This makes it possible to
augment the consumer logs with the `consumerId`, e.g. using pino's child logger:
that takes the `consumerId` and optional `consumerName` of the consuming Feature
App or Feature Service, and returns a [`Logger`][core-logger-interface]
instance. This makes it possible to augment the consumer logs with the
`consumerId` and/or `consumerName`, e.g. using pino's child logger:

```js
import {defineLogger} from '@feature-hub/logger';
Expand All @@ -83,7 +84,9 @@ import {defineLogger} from '@feature-hub/logger';
const {featureAppManager} = createFeatureHub('acme:integrator', {
logger,
featureServiceDefinitions: [
defineLogger(consumerId => logger.child({consumerId}))
defineLogger((consumerId, consumerName) =>
logger.child({consumerId, consumerName})
)
]
});
```
Expand Down
105 changes: 105 additions & 0 deletions docs/guides/integrating-the-feature-hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,32 @@ be provided:
For more details please refer to the the
["Feature App Configs" section](#feature-app-configs).

#### `featureAppName`

The `featureAppName` is used as a consumer name for dependent Feature Services
that can use it for display purposes, logging, looking up Feature App
configuration meta data, etc. In contrast to the `featureAppId`, the
`featureAppName` must not be unique, e.g.:

```jsx
<section>
<div>
<FeatureAppLoader
featureAppId="some-feature-app:main"
featureAppName="some-feature-app"
src="https://example.com/some-feature-app.js"
/>
</div>
<aside>
<FeatureAppLoader
featureAppId="some-feature-app:aside"
featureAppName="some-feature-app"
src="https://example.com/some-feature-app.js"
/>
</aside>
</section>
```

#### `children`

One can pass a rendering function as the React Children (i.e. the `children`
Expand Down Expand Up @@ -404,6 +430,32 @@ be provided:
For more details please refer to the the
["Feature App Configs" section](#feature-app-configs).

#### `featureAppName`

The `featureAppName` is used as a consumer name for dependent Feature Services
that can use it for display purposes, logging, looking up Feature App
configuration meta data, etc. In contrast to the `featureAppId`, the
`featureAppName` must not be unique, e.g.:

```jsx
<section>
<div>
<FeatureAppContainer
featureAppId="some-feature-app:main"
featureAppName="some-feature-app"
featureAppDefinition={someFeatureAppDefinition}
/>
</div>
<aside>
<FeatureAppContainer
featureAppId="some-feature-app:aside"
featureAppName="some-feature-app"
featureAppDefinition={someFeatureAppDefinition}
/>
</aside>
</section>
```

#### `children`

One can pass a rendering function as the React Children (i.e. the `children`
Expand Down Expand Up @@ -575,6 +627,32 @@ document.querySelector('#app').appendChild(featureAppLoader);
For more details please refer to the the
["Feature App Configs" section](#feature-app-configs).

#### `featureAppName`

The `featureAppName` is used as a consumer name for dependent Feature Services
that can use it for display purposes, logging, looking up Feature App
configuration meta data, etc. In contrast to the `featureAppId`, the
`featureAppName` must not be unique, e.g.:

```html
<section>
<div>
<feature-app-loader
featureAppId="some-feature-app:main"
featureAppName="some-feature-app"
src="https://example.com/some-feature-app.js"
></feature-app-loader>
</div>
<aside>
<feature-app-loader
featureAppId="some-feature-app:aside"
featureAppName="some-feature-app"
src="https://example.com/some-feature-app.js"
></feature-app-loader>
</aside>
</section>
```

#### `loading` Slot

The `feature-app-loader` custom element renders a slot named `loading` while the
Expand Down Expand Up @@ -690,6 +768,33 @@ document.querySelector('#app').appendChild(featureAppContainer);
For more details please refer to the the
["Feature App Configs" section](#feature-app-configs).

#### `featureAppName`

The `featureAppName` is used as a consumer name for dependent Feature Services
that can use it for display purposes, logging, looking up Feature App
configuration meta data, etc. In contrast to the `featureAppId`, the
`featureAppName` must not be unique, e.g.:

```js
const mainFeatureAppContainer = document.createElement('feature-app-container');

mainFeatureAppContainer.setAttribute('featureAppId', 'some-feature-app:main');
mainFeatureAppContainer.setAttribute('featureAppName', 'some-feature-app');
mainFeatureAppContainer.featureAppDefinition = someFeatureAppDefinition;

document.querySelector('main').appendChild(mainFeatureAppContainer);

const asideFeatureAppContainer = document.createElement(
'feature-app-container'
);

asideFeatureAppContainer.setAttribute('featureAppId', 'some-feature-app:aside');
asideFeatureAppContainer.setAttribute('featureAppName', 'some-feature-app');
asideFeatureAppContainer.featureAppDefinition = someFeatureAppDefinition;

document.querySelector('aside').appendChild(asideFeatureAppContainer);
```

#### `error` Slot

The `feature-app-container` custom element renders a slot named `error` if the
Expand Down
41 changes: 36 additions & 5 deletions docs/guides/writing-a-feature-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,12 @@ following properties:
The `create` method must return an object that provides an implementation of the
`FeatureServiceBinder` type of the [`@feature-hub/core`][core-api] package for
each supported major version. The Feature Service binder is a function that is
called for each consumer with its `consumerId` string as the first argument. It
returns a Feature Service binding with a consumer-bound `featureService` and an
optional `unbind` method. The `FeatureServiceRegistry` passes this
consumer-bound `featureService` to the consumer's `create` method via the
`env.featureServices` argument.
called for each consumer with its `consumerId` string as the first argument, and
optionally its `consumerName` string as the second argument. It returns a
Feature Service binding with a consumer-bound `featureService` and an optional
`unbind` method. The `FeatureServiceRegistry` passes this consumer-bound
`featureService` to the consumer's `create` method via the `env.featureServices`
argument.

If within its `create` method a Feature Service concludes that it can not be
created, e.g. because of a certain configuration or missing requirement, it
Expand Down Expand Up @@ -307,6 +308,36 @@ const myFeatureServiceDefinition = {
};
```

## When to use the `consumerName` instead of the `consumerId`

In contrast to the `consumerId`, the `consumerName` must not be unique. It can
be used for display purposes, logging, looking up Feature App configuration meta
data, etc., e.g. a Logger Feature Service could use it like this:

```js
const createConsumerConsole = consumer => {
const prefixArgs = [`%c${consumer}`, 'font-weight: bold'];

return {
trace: console.trace.bind(console, ...prefixArgs),
debug: console.debug.bind(console, ...prefixArgs),
info: console.info.bind(console, ...prefixArgs),
warn: console.warn.bind(console, ...prefixArgs),
error: console.error.bind(console, ...prefixArgs)
};
};

const defineLogger = (createConsumerLogger = createConsumerConsole) => ({
id: 'demo:logger',

create: () => ({
'1.0.0': (consumerId, consumerName = consumerId) => ({
featureService: createConsumerLogger(consumerName)
})
})
});
```

[core-api]: /@feature-hub/modules/core.html
[feature-service-binder]:
/docs/guides/writing-a-feature-service#feature-service-binder
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/__tests__/create-feature-hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@ describe('createFeatureHub()', () => {
featureHubOptions
);

expect(mockFeatureServiceV1Binder).toHaveBeenCalledWith(
'test:integrator'
);
expect(mockFeatureServiceV1Binder.mock.calls).toEqual([
['test:integrator', undefined]
]);

expect(featureServices['test:feature-service']).toBe(
mockFeatureServiceV1
Expand Down
46 changes: 44 additions & 2 deletions packages/core/src/__tests__/feature-app-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ describe('FeatureAppManager', () => {
);

expect(mockFeatureServiceRegistry.bindFeatureServices.mock.calls).toEqual(
[[mockFeatureAppDefinition, featureAppId]]
[[mockFeatureAppDefinition, featureAppId, undefined]]
);

const {featureServices} = mockFeatureServicesBinding;
Expand Down Expand Up @@ -230,6 +230,48 @@ describe('FeatureAppManager', () => {
});
});

describe('with a featureAppName', () => {
it('passes the featureAppName as consumerName to bindFeatureServices', () => {
const featureAppId = 'testId';
const featureAppName = 'testName';

featureAppManager = new FeatureAppManager(mockFeatureServiceRegistry, {
logger
});

featureAppManager.createFeatureAppScope(
featureAppId,
mockFeatureAppDefinition,
{featureAppName}
);

expect(
mockFeatureServiceRegistry.bindFeatureServices.mock.calls
).toEqual([[mockFeatureAppDefinition, featureAppId, featureAppName]]);
});

it('creates a Feature App with a consumer environment that includes the featureAppName', () => {
const featureAppId = 'testId';
const featureAppName = 'testName';

featureAppManager = new FeatureAppManager(mockFeatureServiceRegistry, {
logger
});

featureAppManager.createFeatureAppScope(
featureAppId,
mockFeatureAppDefinition,
{featureAppName}
);

const {featureServices} = mockFeatureServicesBinding;

expect(mockFeatureAppCreate.mock.calls).toEqual([
[{featureServices, featureAppId, featureAppName}]
]);
});
});

describe('without an ExternalsValidator provided to the FeatureAppManager', () => {
describe('with a Feature App definition that is declaring external dependencies', () => {
beforeEach(() => {
Expand Down Expand Up @@ -426,7 +468,7 @@ describe('FeatureAppManager', () => {

expect(
mockFeatureServiceRegistry.bindFeatureServices.mock.calls
).toEqual([[mockFeatureAppDefinition, featureAppId]]);
).toEqual([[mockFeatureAppDefinition, featureAppId, undefined]]);

expect(featureServiceRegistryMethodCalls).toEqual([
'registerFeatureServices',
Expand Down
Loading