I have a BFF needs to send some requests to ServiceA.
ServiceA is providing some API (GET, POST, ...) that we can deal with.
For example:
#Get('greeting')
getGreetingMessage(#Param('name') name: string): string {
return `Hello ${name}`;
}
In MicroService Architecture in NestJs I see the best practice in BFF to send requests to other servcies is to use Message patterns like cmd with payloads.
For example
constructor(
#Inject('SERVICE_A') private readonly clientServiceA: ClientProxy,
) {}
getGreetingFromServiceA() {
const startTs = Date.now();
const pattern = { cmd: 'greeting' };
const payload = {};
return this.clientServiceA
.send<string>(pattern, payload)
.pipe(
map((message: string) => ({ message, duration: Date.now() - startTs })),
);
}
So to do that I have to support MessagePattern in ServiceA like:
#MessagePattern({cmd: 'greeting'})
getGreetingMessage(name: string): string {
return `Hello ${name}`;
}
So my question is Is there a way to append MessagePattern to exisiting APIs in ServiceA? so I can call them with 2 different ways either by Rest GET Request or MessagePattern from BFF.
I'm thinking about using 2 docerators (Get and MessagePattern)
Like that
#Get('greeting')
#MessagePattern({cmd: 'greeting'})
getGreetingMessage(#Param('name') name: string): string {
return `Hello ${name}`;
}
If no, so how can I use a proxyClient to make http requests to other microservice in the BFF?
Actually it is not possible in NestJS to define more than one decorator to the same method in the controller but we make it a hybrid application which supports different communication protocols so we can call it via TCP or HTTP and so on like in this example https://docs.nestjs.com/faq/hybrid-application
i'm trying to make a tcp version of gateway. and this is the gateway code . it's use httpclient
NettyInbound serverInbound;
NettyOutbound serverOutbound;
Map map;
Flux<HttpClientResponse> flux = HttpClient.create()
.port(8080)
.request(HttpMethod.POST)
.send((req, outbound) -> {
// send infomation
return outbound.send(serverInbound.receive().retain());
}).responseConnection((res, connection) -> {
// save connection
map.put("connection",connection);
return Mono.just(res);
});
flux.then(Mono.defer(()->{
// this is after the gateway server receiving from backend and send to the front
Connection connection = map.get("connectoin");
return serverOutbound.send(connection.inbound().receive().retain());
}));
and i'm trying to do the same thing with tcpclient
TcpClient client = TcpClient.create()
.port(8900);
TcpServer.create()
.port(8080)
.handle((inbound,outbound)->{
// must be something wrong with it
return client.connect().flatMap(con->{
NettyInbound cin = con.inbound();
NettyOutbound cout = con.outbound();
return cout.send(inbound.receive().retain()).then().map(ss->{
return cin;
});
}).map(cin->{
return outbound.send(cin.receive());
}).then();
}).bindNow()
.onDispose()
.block();
but it won't work . is anything i can do to fulfill the functionality like responseConnection ?
I'm connecting to RSKj using the following endpoint:
ws://localhost:4444/
... However, I am unable to connect.
Note that the equivalent HTTP endpoint http://localhost:4444/
work for me, so I know that my RSKj node is running properly.
I need to listen for newBlockHeaders, so I prefer to use WebSockets (instead of HTTP).
How can I do this?
RSKj by default uses 4444 as the port for the HTTP transport;
and 4445 as the port for the Websockets transport.
Also note that the websockets endpoint is not at /,
but rather at websocket.
Therefore use ws://localhost:4445/websocket as your endpoint.
If you're using web3.js,
you can create a web3 instance that connects over Websockets
using the following:
const Web3 = require('web3');
const wsEndpoint = 'ws://localhost:4445/websocket';
const wsProvider =
new Web3.providers.WebsocketProvider(wsEndpoint);
const web3 = new Web3(wsProvider);
The second part of your question can be done
using eth_subscribe on newBlockHeaders.
Using the web3 instance from above like so:
// eth_subscribe newBlockHeaders
web3.eth.subscribe('newBlockHeaders', function(error, blockHeader) {
if (!error) {
// TODO something with blockHeader
} else {
// TODO something with error
}
});
For a API request I send the jwt with authentication header while request. So I get informations from the jwt. But with websocket it is not possible to set headers.
I also do not want to set the jwt as data in every request. So I want to implement a authentication (server side), witch save data from the jwt to a redis DB with the socket id as an key.
Now I have the problem, that the socket or socket id is not part of the feathers context in hooks or only available as symbol witch I do not figure out how to get it right now. But I think there have to be a more elegant version of how to save data in combination with sockets and get theme back again.
Which is the best way to save user data in a socket connection, to not send the data everytime again?
Mabye it will also help if somebody can tell me how to read the Symbol in the following structur:
connection: {provider: "socketio", Symbol(#feathersjs/socketio/socket): Socket}
How to authenticate a socket connection without the Feathers client is described in the API documentation here. This can be done by authenticating the connection like this:
const io = require('socket.io-client');
const socket = io('http://localhost:3030');
socket.emit('create', 'authentication', {
strategy: 'local',
email: 'hello#feathersjs.com',
password: 'supersecret'
}, function(error, authResult) {
console.log(authResult);
// authResult will be {"accessToken": "your token", "user": user }
// You can now send authenticated messages to the server
});
Or by sending the existing access token when establishing the socket connection:
const io = require('socket.io-client');
const socket = io('http://localhost:3030', {
extraHeaders: {
Authorization: `Bearer <accessToken here>`
}
});
I found a soltuion in a simular question: How to add parameters to a FeathersJS socket connection
It is possible to save information to the connection by adding it to socket.feathers and it will added as params in every following request.
Socketio Middleware
const jwtHandler = (feathers, authorization, next) => {
const regex = /Bearer (.+)/g;
const jwt = (regex.exec(authorization) || [])[1]; // todo length >= 2 test.
try {
const { accountId, userId } = decode(jwt);
feathers.accountId = accountId;
feathers.userId = userId;
feathers.authorization = authorization;
} catch (err) {
throw new Forbidden('No valid JWT', err);
}
next();
};
const socketJwtHandler = io => io.use((socket, next) => {
jwtHandler(socket.feathers, socket.handshake.query.authorization, next);
});
and than calling context.param.userId in hooks.
I'm looking into implementing a "subscription" type using server-sent events as the backing api.
What I'm struggling with is the interface, to be more precise, the http layer of such operation.
The problem:
Using the native EventSource does not support:
Specifying an HTTP method, "GET" is used by default.
Including a payload (The GraphQL query)
While #1 is irrefutable, #2 can be circumvented using query parameters.
Query parameters have a limit of ~2000 chars (can be debated)
which makes relying solely on them feels too fragile.
The solution I'm thinking of is to create a dedicated end-point for each possible event.
For example: A URI for an event representing a completed transaction between parties:
/graphql/transaction-status/$ID
Will translate to this query in the server:
subscription TransactionStatusSubscription {
status(id: $ID) {
ready
}
}
The issues with this approach is:
Creating a handler for each URI-to-GraphQL translation is to be added.
Deploy a new version of the server
Loss of the flexibility offered by GraphQL -> The client should control the query
Keep track of all the end-points in the code base (back-end, front-end, mobile)
There are probably more issues I'm missing.
Is there perhaps a better approach that you can think of?
One the would allow a better approach at providing the request payload using EventSource?
Subscriptions in GraphQL are normally implemented using WebSockets, not SSE. Both Apollo and Relay support using subscriptions-transport-ws client-side to listen for events. Apollo Server includes built-in support for subscriptions using WebSockets. If you're just trying to implement subscriptions, it would be better to utilize one of these existing solutions.
That said, there's a library for utilizing SSE for subscriptions here. It doesn't look like it's maintained anymore, but you can poke around the source code to get some ideas if you're bent on trying to get SSE to work. Looking at the source, it looks like the author got around the limitations you mention above by initializing each subscription with a POST request that returns a subscription id.
As of now you have multiple Packages for GraphQL subscription over SSE.
graphql-sse
Provides both client and server for using GraphQL subscription over SSE. This package has a dedicated handler for subscription.
Here is an example usage with express.
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-sse';
// Create the GraphQL over SSE handler
const handler = createHandler({ schema });
// Create an express app serving all methods on `/graphql/stream`
const app = express();
app.use('/graphql/stream', handler);
app.listen(4000);
console.log('Listening to port 4000');
#graphql-sse/server
Provides a server handler for GraphQL subscription. However, the HTTP handling is up to u depending of the framework you use.
Disclaimer: I am the author of the #graphql-sse packages
Here is an example with express.
import express, { RequestHandler } from "express";
import {
getGraphQLParameters,
processSubscription,
} from "#graphql-sse/server";
import { schema } from "./schema";
const app = express();
app.use(express.json());
app.post(path, async (req, res, next) => {
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query,
};
const { operationName, query, variables } = getGraphQLParameters(request);
if (!query) {
return next();
}
const result = await processSubscription({
operationName,
query,
variables,
request: req,
schema,
});
if (result.type === RESULT_TYPE.NOT_SUBSCRIPTION) {
return next();
} else if (result.type === RESULT_TYPE.ERROR) {
result.headers.forEach(({ name, value }) => res.setHeader(name, value));
res.status(result.status);
res.json(result.payload);
} else if (result.type === RESULT_TYPE.EVENT_STREAM) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
});
result.subscribe((data) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
req.on('close', () => {
result.unsubscribe();
});
}
});
Clients
The two packages mentioned above have companion clients. Because of the limitation of the EventSource API, both packages implement a custom client that provides options for sending HTTP Headers, payload with post, what the EvenSource API does not support. The graphql-sse comes together with it client while the #graphql-sse/server has companion clients in a separate packages.
graphql-sse client example
import { createClient } from 'graphql-sse';
const client = createClient({
// singleConnection: true, use "single connection mode" instead of the default "distinct connection mode"
url: 'http://localhost:4000/graphql/stream',
});
// query
const result = await new Promise((resolve, reject) => {
let result;
client.subscribe(
{
query: '{ hello }',
},
{
next: (data) => (result = data),
error: reject,
complete: () => resolve(result),
},
);
});
// subscription
const onNext = () => {
/* handle incoming values */
};
let unsubscribe = () => {
/* complete the subscription */
};
await new Promise((resolve, reject) => {
unsubscribe = client.subscribe(
{
query: 'subscription { greetings }',
},
{
next: onNext,
error: reject,
complete: resolve,
},
);
});
;
#graphql-sse/client
A companion of the #graphql-sse/server.
Example
import {
SubscriptionClient,
SubscriptionClientOptions,
} from '#graphql-sse/client';
const subscriptionClient = SubscriptionClient.create({
graphQlSubscriptionUrl: 'http://some.host/graphl/subscriptions'
});
const subscription = subscriptionClient.subscribe(
{
query: 'subscription { greetings }',
}
)
const onNext = () => {
/* handle incoming values */
};
const onError = () => {
/* handle incoming errors */
};
subscription.susbscribe(onNext, onError)
#gaphql-sse/apollo-client
A companion package of the #graph-sse/server package for Apollo Client.
import { split, HttpLink, ApolloClient, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { ServerSentEventsLink } from '#graphql-sse/apollo-client';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const sseLink = new ServerSentEventsLink({
graphQlSubscriptionUrl: 'http://localhost:4000/graphql',
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
sseLink,
httpLink
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
If you're using Apollo, they support automatic persisted queries (abbreviated APQ in the docs). If you're not using Apollo, the implementation shouldn't be too bad in any language. I'd recommend following their conventions just so your clients can use Apollo if they want.
The first time any client makes an EventSource request with a hash of the query, it'll fail, then retry the request with the full payload to a regular GraphQL endpoint. If APQ is enabled on the server, subsequent GET requests from all clients with query parameters will execute as planned.
Once you've solved that problem, you just have to make a server-sent events transport for GraphQL (should be easy considering the subscribe function just returns an AsyncIterator)
I'm looking into doing this at my company because some frontend developers like how easy EventSource is to deal with.
There are two things at play here: the SSE connection and the GraphQL endpoint. The endpoint has a spec to follow, so just returning SSE from a subscription request is not done and needs a GET request anyway. So the two have to be separate.
How about letting the client open an SSE channel via /graphql-sse, which creates a channel token. Using this token the client can then request subscriptions and the events will arrive via the chosen channel.
The token could be sent as the first event on the SSE channel, and to pass the token to the query, it can be provided by the client in a cookie, a request header or even an unused query variable.
Alternatively, the server can store the last opened channel in session storage (limiting the client to a single channel).
If no channel is found, the query fails. If the channel closes, the client can open it again, and either pass the token in the query string/cookie/header or let the session storage handle it.