Skip to content

Commit

Permalink
Merge edf5b33 into d88c525
Browse files Browse the repository at this point in the history
  • Loading branch information
andrzejewsky committed Mar 8, 2021
2 parents d88c525 + edf5b33 commit f5bcdd5
Show file tree
Hide file tree
Showing 7 changed files with 1,725 additions and 133 deletions.
21 changes: 19 additions & 2 deletions packages/core/docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ module.exports = {
head: [
['link', { rel: 'icon', href: '/favicon.png' }]
],
configureWebpack: (config) => {
config.module.rules = config.module.rules.map(rule => ({
...rule,
use: rule.use && rule.use.map(useRule => ({
...useRule,
options: useRule.loader === 'url-loader' ?
/**
Hack for loading images properly.
ref: https://github.com/vuejs/vue-loader/issues/1612#issuecomment-559366730
*/
{ ...useRule.options, esModule: false } :
useRule.options
}))
}))
},
themeConfig: {
logo: 'https://camo.githubusercontent.com/48c886ac0703e3a46bc0ec963e20f126337229fc/68747470733a2f2f643968687267346d6e767a6f772e636c6f756466726f6e742e6e65742f7777772e76756573746f726566726f6e742e696f2f32383062313964302d6c6f676f2d76735f3062793032633062793032633030303030302e6a7067',
nav: [
Expand Down Expand Up @@ -172,12 +187,14 @@ module.exports = {
title: 'Advanced [WIP]',
collapsable: false,
children: [
['/advanced/architecture', 'Architecture'],
['/advanced/context', 'Application Context'],
['/advanced/calling-platform-api', 'Calling Platform Api'],
['/advanced/server-middleware', 'Server Middleware'],
['/advanced/internationalization', 'Internationalization'],
['/advanced/performance', 'Performance'],
['/advanced/ssr-cache', 'SSR Cache'],
['/advanced/logging', 'Logging'],
['/advanced/architecture', 'Architecture']
['/advanced/logging', 'Logging']
]
},
{
Expand Down
26 changes: 26 additions & 0 deletions packages/core/docs/advanced/calling-platform-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Calling platform API

The Vue Storefront has its own way to communicate with other platform APIs. First of all, each integration implements an API-client that defines an interaction and connection with a given platform. Each time you make a call to the external platform, you actually use API-client beneath. We use that in all of the integrations and also you can reach the API-client in your project if it's needed.

## How do Integrations work?

Integration API-client is a sort of SDK for a given platform you integrate with. It has a set of functions, each is dedicated to one action, endpoint or a feature eg. `getProduct` , `loadCart` `addToCart`

## Accessing integration methods on the frontend

To access API-client functions, you can use the composable function `useVSFContext` that reads from the application context every possible integration so you can easily access it.

```ts
// each platform has different tag to access its methods, eg $spryker, $storyblok etc.
const { $ct } = useVSFContext();

$ct.api.getProduct({ id: 1 })
```

In the example above we have accessed the API-client for commercetools (Each integration has dedicated tag name, look at [context docs](/advanced/context)) and we called function `getProduct`.



## Extending API Client

Sometimes, it's necessary to override the original behavior either API-client or even an entire request that comes from an external platform. The Vue Storefront also provides possibility to do this by using [middleware extensions](/advanced/server-middleware)
138 changes: 138 additions & 0 deletions packages/core/docs/advanced/server-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Server middleware

The server middleware is a part of Vue Storefront networking. That's the way of making connections with the eCommerce platform and also a solution to reduce bundle size, number of calls, credentials sharing, extensibility handling, and more.

## What is Vue Storefront Middleware and why we need it?

The Vue Storefront middleware is an express.js proxy that taking the requests from the front-end translates them to a certain integration and call-related API-client.

We have implemented it for a variety of reasons.

First of all, it allows us to provide a prover way of extensibility - As a developer, you have control of the requests and the responses in the given platform and you can write an extension to add something extra in your project.

A front-end application sometimes needs an additional API endpoint - that's also possible since you have a server written in express.js.

All credentials for your platform are stored only on the backend side, there are no shared keys in the browser.

## How it works (in a nutshell)

The way of how it works represents the following diagram:

<center>
<img src="../images/middleware-diagram.jpg" alt="Middleware Diagram" />
</center>

In the Vue Storefront platform, you always have a few components: Core, UI, Middleware, and integration part: composables, API-client (and sometimes UI). As we mentioned before, API-client is being called only on the middleware, but you still can access it on the front-end side - how is that possible?

When you access an API-client on the front-end side, you are accessing actually a stub, instead of a real API-client instance. This stub makes a call to the middleware (Remote Procedure Call), and ask for loading a specific integration, and executing specific function.

Middleware recognizes this by the tag name of integration and the function name that needs to be called.

When the middleware has loaded an API-client (integration) it proceeds to create a connection and make a requested API-client function call. Within this whole process, all of the extensions are being executed. Once the middleware has finished its job, the response backs to the front-end side as if it was transferred using a direct connection.


## Configuration

When it comes to configuration, you only need to tell middleware what the integrations you have along with their credentials. There is a dedicated config to do that called `middleware.config.js` that contains a section with integrations definition (`integrations`).

Each entry under this section starts with a tag name of given integration, and contains an object with the following fields:

- `location` - points to the package of the API-client, related to given integration (server entry point)
- `configuration` - contains a configuration of given integration, such as credentials and others
- `extensions` - a function that returns a extensions (jump to the next section)
- `customQueries` - section that contains custom queries (graphql only)

```js
module.exports = {
integrations: {
<TAG NAME>: {
location: '@<integration-package>/server',
configuration: {}
extensions: (extensions) => extensions,
customQeries: {}
}
}
};
```

## Extending Middleware

Middleware allows you to inject into the lifecycle of the entire network flow, starting with configuring a connection and ending with a final response. To use those things, we created an extension feature.

You can define as many extensions as you want. Remember they are always correlated with a specyfic API-client (integration). How they look like? Each extension has the followin structure:

```js
const extension = {
name: 'extension-name',
extendApiMethods: {
getProduct: async () => { /* ... */ }
},
hooks: (req, res) => {
return {
beforeCreate: ({ configuration }) => configuration,
afterCreate: ({ configuration }) => configuration,
beforeCall: ({ configuration, callName, args }) => args,
afterCall: ({ configuration, callName, args, response }) => response
}
}
}
```

- `name` - defines the unique name of an extension
- `extendApiMethods` - overides the original functions from API-client
- `hooks` - defines lifecycle hooks of API-client
- `hooks:beforeCreate` - called before API-client creates a connection, takes the given configuration as an argument, and must return the configuration. In this place, you can attach something else to the configuration or even change it.
- `hooks:afterCreate` - Similar to the previous one, but called after the connection has been created. It also returns a configuration and you can change it.
- `hooks:beforeCall` - Triggered before each API-client function. We have access to the configuration, function name, and its arguments. This function must return the arguments and based on the input parameters we can change it.
- `hooks:afterCall` - Triggered after each API-client function.We have access to the configuration, function name, and its arguments. This function must return the response and based on the input parameters we can attach something to it.


To register a created extension, we have to add it do the middleware config file:

```js
module.exports = {
integrations: {
<TAG NAME>: {
location: '@<integration-package>/server',
configuration: {}
extensions: (extensions) => [
...extensions,
{
name: 'our-extension'
hooks: () => { /* ... */}
}
],
customQeries: {}
}
}
};
```

## Separating middleware from Nuxt

By default, Vue Storefront middleware is running within the Nuxt.js process. Sometimes there is a need to disconnect it from the app, and run it as a separate instance, and independent process.

Since is the real express.js application, you can do this, by creating file:

```js
const { createServer } = require('@vue-storefront/middleware');
const { integrations } = require('./middleware.config');

const app = createServer({ integrations });

app.listen(8181, () => {
console.log('Middleware started');
});
```

Now, when you run this using node, your middleware should work separately.

Additionally, you need to remove the middleware module entry from the nuxt.config.js and configure the domain, where your middleware is settled.

```js
export default {
publicRuntimeConfig: {
middlewareUrl: 'https://api.commerce.com'
}
}
```
10 changes: 9 additions & 1 deletion packages/core/docs/general/key-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ const { $ct } = useVSFContext()

You can read more about Vue Storefront Context [here](/advanced/context)


## Middleware

When it comes to the networking layer, Vue Storefront uses a middleware that's is a bridge between front-end and other backends (eCommerce or 3rd party services). The front-end always calls middleware that is redirecting requests to correlated destinations. It allows developers to implement a custom logic that injects into the lifecycle of the requests or even create custom API endpoints if it's needed.

You can read more about Vue Storefront Middleware [here](/advanced/server-middleware)


## Integrations

Even though high-level APIs are the same for all Vue Storefront integrations they're different on the low level (data formats, search params). Check the docs of a specific platform on the left side under "eCommerce integrations" tab to learn about them.
Even though high-level APIs are the same for all Vue Storefront integrations they're different on the low level (data formats, search params). Check the docs of a specific platform on the left side under the "eCommerce integrations" tab to learn about them.
44 changes: 44 additions & 0 deletions packages/core/docs/guide/composables.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,47 @@ watch(error, (error, prevError) => {
In this example, we are using `useUiNotification` - a composable that handles notifications state. You can read more about it in the API reference.

[//]: # 'TODO: This should be added to API reference'

### How to customize graphql queries?

If the integration you are currently using has GraphQL API, you may need to change the default query that is being sent to fetch the data. That's quite a common case so Vue Storefront also provides that ability.
Since the comminication with an API goes over the our middleware, all of the queries also are defined there.
To customize or even totally override the original (default) queries you need to follow two steps.
Firstly, you need to use a dedicated parameter: `customQuery` that tells the app, what to do with a query.
This parameter is an object that has a name of the queries as keys, and the name of the queries function under the values.
```ts
const { search } = useProduct();
search({ customQuery: { products: 'my-products-query' } });
```
In the example above, we are changing `products` query, and our function that will take care of this overriding is `my-products-query`. As a second step, we need to define that function.
Each definition of query function we can find in the `middleware.config.js`. Therefore, this is the place where we define `my-products-query`:
```js
module.exports = {
integrations: {
ct: {
location: '@vue-storefront/commercetools-api/server',
configuration: { /* ... */ },
customQueries: {
'my-products-query': ({ query, variables }) => {
variables.locale = 'en'
return { query, variables }
}
}
}
}
};
```
The custom query function always has in the arguments the default query and default variables and must return the query and its variables as well. In the body you can do anything you want with those parameters - you can override them or even change to the new ones.
Binary file added packages/core/docs/images/middleware-diagram.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f5bcdd5

Please sign in to comment.