Skip to content

Instantly share code, notes, and snippets.

@thebigredgeek
Last active April 24, 2017 01:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thebigredgeek/3e5d9757b054b642062569424f7fee03 to your computer and use it in GitHub Desktop.
Save thebigredgeek/3e5d9757b054b642062569424f7fee03 to your computer and use it in GitHub Desktop.
RFC: "Solaris"
{
"redis": {
"host": "localhost",
"port": 6379,
"prefix": "solaris-"
},
"port": 3000
}
import { publish, channel, schema, gql, type, Service } from 'solaris';
@type('Post')
@schema(gql`
type Post {
id: ID!
text: String
}
# Post lives elsewhere
extend type User {
# Magically binds to getPostsForAuthor function below (see decorator)
Posts: [Post]!
}
input CreatePostInput {
text: String
}
extend type Mutation {
# Automatically binds to the `createPost` function below
createPost (input: CreatePostInput): Post
}
extend type Subscription {
# Automatically binds to the `postsSubscription` function below
postsSubscription(input: PostSubscriptionInput!): Post
}
input PostSubscriptionInput {
AuthorId: ID
}
`)
export default class User extends Service {
@publish(post => { // Publish the returned value under the `userChannel` channel
channel: 'postsChannel',
object: post.toJSON()
})
// Resolver for createPost
async createPost (root, args, context) {
return context.models.post.create(args.input, context.user);
}
// Resolver for postsSubscription
async postsSubscription (post, args, context) {
return post;
}
// Kinda like setupFunctions for graphql-subscriptions?
@channel({
for: 'postsSubscription'
})
async postsChannel (post, args, context) {
const { input: { AuthorId } } = args;
// Filter by AuthorId, if specified
return AuthorId ? post.AuthorId === AuthorId : true;
}
@edge({
type: 'User',
field: 'Posts'
})
getPostsForAuthor (user, args, context) {
return context.models.post.findByAuthorId(user.id);
}
}
import { publish, channel, schema, gql, type, Service } from 'solaris';
@type('User')
@schema(gql`
type User {
id: ID!
name: String
email: Email
}
# Post lives elsewhere
extend type Post {
# Magically binds to getAuthorForPost function below
Author: User!
}
input UpdateUserInput {
id: ID!
name: String
email: String
}
extend type Mutation {
# Automatically binds to the `updateUser` function below
updateUser (input: UpdateUserInput): User
}
extend type Subscription {
# Automatically binds to the `userSubscription` function below
userSubscription (input: UserSubscriptionInput!): User
}
input UserSubscriptionInput {
id: ID
}
`)
export default class User extends Service {
@publish(user => { // Publish the returned value under the `userChannel` channel
channel: 'userChannel',
object: user.toJSON()
})
// Resolver for updateUser
async updateUser (root, args, context) {
return context.models.user.update(args.input);
}
// Resolver for userSubscription
async userSubscription (user, args, context) {
return user;
}
// Kinda like setupFunctions for graphql-subscriptions?
@channel({
for: 'userSubscription'
})
async userChannel (user, args, context) {
const { user: subscribedUser } = context;
return user.id === subscribedUser.id;
}
@edge({
type: 'Post',
field: 'Author'
})
getAuthorForPost (post, args, context) {
return context.models.user.findById(post.AuthorId);
}
}
@thebigredgeek
Copy link
Author

Server is bootstrapped by running solaris from the CLI. Batteries included: body parsing, pluggable auth with later example, WS based subscriptions with redis pub/sub, logging, etc.

Authentication could easily be implemented with additional decorators / HOF.

Boilerplate persistent storage + search via Mongo + ElasticSearch could also be easily implemented here, though optional, with separate package(s) like solaris-mongo and solaris-elasticsearch. The classes could double as models, with context factories and the like. Through this, you could do something like:

@mongo({
  id: mongo.oid,
  name: mongo.string,
  email: mongo.string
})
@type('User')
@schema(gql`
`)
export default class User {
  ...
}

From this, you'd get this.mongo or something like it. The same could be implemented with Elasticsearch. If we took this route, we'd need to provide lifecycle hooks as well... such as createContext and destroyContext functions.

As is, solaris would boot all service files into the same NodeJS process. However, organizing code this way with abstracted transport makes breaking into micro services trivial. A solaris-cluster lib could be easily written, which could serve as an API facade and dispatch portions of the query down to discrete services each on their own docker image. The entire cluster could be built via solaris build, which would pre-aggregate GQL types, distribute them between containers, etc... with the facade service handling routing to resolve entire queries with multiple services without having to think about how to handle transport etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment