Skip to content

Commit

Permalink
Refine Facades docs
Browse files Browse the repository at this point in the history
  • Loading branch information
aedart committed Apr 9, 2024
1 parent ed3242a commit dfafaa5
Showing 1 changed file with 114 additions and 21 deletions.
135 changes: 114 additions & 21 deletions docs/archive/current/packages/support/facades/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ sidebarDepth: 0
# Introduction <Badge type="tip" text="Available since v0.11" vertical="middle" /><Badge type="success" text="Browser" vertical="middle" />

The `@aedart/support/facades` package is an adaptation of [Laravel's Facades](https://laravel.com/docs/11.x/facades)
(_originally licensed under [MIT](https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/LICENSE.md)_). A [Facade](https://en.wikipedia.org/wiki/Facade_pattern) acts as an interface
to an underlying object instance.
(_originally licensed under [MIT](https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/LICENSE.md)_). In this context, a [Facade](https://en.wikipedia.org/wiki/Facade_pattern) acts as an interface (_or gateway_) to an
underlying object instance, resolved from the [Service Container](../../container/README.md).

```js
import { Container } from "@aedart/support/facades";
Expand Down Expand Up @@ -40,8 +40,7 @@ Facade.destroy();

## Define a Facade

To define your own Facade, extend the abstract `Facade` class. Specify the target [binding identifier](../../container/bindings.md#identifiers),
and the `obtain()` method.
To define your own Facade, extend the abstract `Facade` class, and specify the target [binding identifier](../../container/bindings.md#identifiers).

```js
import { Facade } from "@aedart/support/facades";
Expand All @@ -52,25 +51,34 @@ export default class ApiFacade extends Facade
{
return 'api_client';
}
}
```

/**
* @inheritDoc
*
* @return {import('@acme/api').ApiClient}
*/
static obtain()
If you are using TypeScript, then you can also specify the return type of the `obtain()` method, by declaring the
underlying resolved object's type, for the internal `type` property (_`type` property is not used for any other purpose_).

```ts
import type { Identifier } from "@aedart/contracts/container";
import { Facade } from "@aedart/support/facades";
import type { AcmeApiClient } from "@acme/contracts/api";

export default class ApiFacade extends Facade
{
protected static type: AcmeApiClient;

public static getIdentifier(): Identifier
{
return this.resolveIdentifier();
return 'api_client';
}
}
```

### The `obtain()` method
## The `obtain()` method

Since a facade is only an "interface" to an underlying object instance, that has been registered in a [Service Container](../../container/README.md),
you must specify how that object instance must be resolved. Typically, invoking the internal `resolveIdentifier()` will be sufficient.
However, in special circumstances, you might need to resolve a binding differently, or perhaps perform
some kind of additional post-resolve logic.
The `obtain()` is used to obtain the Facade's underlying object instance. Typically, you do not need to do anything more
than to implement the `getIdentifier()` method in your concrete facade class.
But, in some situations you might need to resolve a binding differently. Or, perhaps perform some kind of additional
post-resolve logic, in order to make easier / simpler to work with the resolved object.

```js
export default class LimitedApiFacade extends Facade
Expand All @@ -81,13 +89,11 @@ export default class LimitedApiFacade extends Facade
}

/**
* @inheritDoc
*
* @return {import('@acme/api').ApiClient}
* @return {import('@acme/contracts/api').AcmeApiClient}
*/
static obtain()
{
const client = this.resolveIdentifier();
const client = this.resolve(this.getIdentifier());
client.error_response_thresshold = 3;
client.ttl = 350;

Expand All @@ -96,6 +102,93 @@ export default class LimitedApiFacade extends Facade
}
```

```js
const promise = LimitedApiFacade.obtain().fetch('https://acme.com/api/users');
```

## Testing

TODO: ....
When you need to test components that rely on Facades, you can register a "spy" (_mocked object_), via the static
method `spy()`. Consider, for instance, that you have a users repository component that relies on a custom Api facade.

```js
import { ApiFacade } from "@acme/facades";

class UsersRepository {

fetch() {
return ApiFacade.obtain().fetch('https://acme.com/api/users');
}

// ...remaining not shown...
}
```

In your testing environment, you can specify a callback that can be used to create a fake object (_mocked object_) that
must behave in a certain way, via the `spy()` method. The callback must return either of the following:

* The Facade's underlying resolved object.
* Or, a fake object that behaves as desired (_in the context of your test_).

```js
ApiFacade.spy((container, identifier) => {
// ...mocking not shown ...

return myResolvedObject; // resolved or mocked object
});
```

All subsequent calls to the facade's underlying object will be made to the registered "spy" object instead.

The following example uses [Jasmine](https://jasmine.github.io/) as testing framework.
However, the `spy()` method is not tied to any specific testing or object mocking framework. Feel free to use whatever
testing tools or frameworks fits your purpose best.

```js
import { ApiFacade } from "@acme/facades";
import { UsersRepository } from "@app";

// E.g. testing via Jasmine Framework...
describe('@acme/api', () => {

// Test setup not shown in this example...

afterEach(() => {
Facade.destroy();
});

it('can obtain users', () => {

let mocked = null;
ApiFacade.spy((container, identifier) => {
const apiClient = container.get(identifier);

mocked = spyOn(apiClient, 'fetch')
.and
.returnValue([
{ id: 12, name: 'Jackie' },
{ id: 14, name: 'Lana' },
// ...etc
]);

// return the resolved api client...
return apiClient;
});

const repo = new UsersRepository();
const users = repo.fetch();

expect(users)
.not
.toBeUndefined();

expect(mocked)
.toHaveBeenCalled();
});
});
```

## Onward

Please consider reading Laravel's ["When to Utilize Facades"](https://laravel.com/docs/11.x/facades#when-to-use-facades),
to gain an idea of when using Facades can be good, and when not.

0 comments on commit dfafaa5

Please sign in to comment.