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

Example usage of new internal utils api #17

Merged
merged 68 commits into from Feb 28, 2017

Conversation

michaloo
Copy link
Contributor

I'm creating this PR to get an agreement on new API and context dependency container.

@michaloo
Copy link
Contributor Author

},

// superset of Hull API - helpers/utils:
agent: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all those methods should be standard methods in hull client

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to standard client.

del: () => {},
},

// OPTIONAL - method to queue jobs to internal queue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method name should be a verb. either enqueue or perform_async / perform_later. We have also see that some jobs need to be scheduled / performed in the future. You can also have a look at Sidekiq's API which works quite well for us already ;)

https://github.com/mperham/sidekiq/wiki

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to enqueue.

// yet may be adjusted freely by ship developer
service: {
client: {
get: () => {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess actions performed on the service by the ship should be encapsulated in the service agent. I think we should keep this structure as simple as possible. No need to expose the client and agent separately here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simplify that for the default architecture. The structure here is elastic and allows connector developer to pick best solution.

* @param {Object} user Hull user object
* @return {Boolean}
*/
export default function filterUserSegments(ctx, user) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michaloo as discussed earlier let's make the path configurable, and / or allow to pass a list of segment Ids also

if (segment) {
search.segment_id = segment.id;
}
const url = URI(`https://${hostname}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would pass the hostname explicitly here. Not use the ctx directly.
hostname is bot available in a worker context, right ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All helpers functions needs adjustments, I've bound them to the context for the simplicity and consistency of the signature, but I agree it's too heavy dependency for most of them. I will rework them.

const router = Router();
router.use(requireHullMiddleware);
router.post("/", (req, res, next) => {
return Promise.resolve(handler(req.hull, _.pick(req, ["body", "query"]))).then(next, next);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would directly pass the whole req object to the handler here. @michaloo do you see any reason not to do so ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it quite confusing if I have fn(req.hull, req) as arguments, since I can get first from the second one. What would you tell about:

handler(req.hull, _.omit(req, ["hull"]))?

* @param {Object} res
* @param {Function} next
*/
export default function segmentsMiddleware(result, req, res, next) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops wrong function name ?

@@ -0,0 +1,37 @@
import _ from "lodash";

const fieldPath = "ship.private_settings.synchronized_segments";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use synchronized_segments as a naming convention and default value here but let's make this configurable.

```js
import Hull from "hull";

const app = new Hull.App({ port, hostSecret, service });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want to take over the app. We want to be an enhancement to an express app.
for 2 reasons

1 - it's less scary. People know how express works, and they understand it.
2 - sometimes (such as Slack bot or when integrating in your own stack) you just can't create a new app, you have to enhance an existing one in a specific way. Look at https://github.com/hull-ships/hull-slack/blob/develop/server/server.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unity just to sum up our discussion here - I all agree with your arguments, I find the express based architecture a good choice too.

I will just tell you that all that parts going far from plain express app, are caused by slightly different approach which I had in mind. After discussions with you and @sbellity trying to bring together your perspectives I came to a conclusion that the express application is only some part of our ship application, btw. the part which could be replaced or changed in some point in the future (for other http framework as koa or dropped for other solution).

But after seeing all the discussion here, I clearly see that you are all for having the express as a first citizen of the whole architecture. So this is my proposition which should reconcile everybody here :)

  1. Let's make the sacred const app = express(); line being called within the ship
  2. Let's make the current Hull.App a class returning a set of middlewares, routers and commands (so a method which takes the app and mutates it, applying some custom logic) - my perspective here is if I were the external ship developer I would find the app = express approach most comfortable for the reason you've brought here up. Yet observing the first actual developers doing their first ships I see that they are much much more focused on the business logic, the parts related to the 3rd party API that to the ships internals or infrastructure parts. I would say that the mind flow of a developer could be totally opposite to the natural flow of the application. That's why I will suggest having a "super command" method where you can start very easily having all the basics set up on our current convention (like manifest, README, templates), then you can turn it off and switch to more low level elements.


// ... application configuration

app.start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here.

I don't know what app is but I know it's not express, and start() doesnt let me decide on the port in the place I'm used to. I don't know what routes are available, exposed and configured. Black box

As a regular Node developer knows app to be an express instance. Here it's app but it's something different.

```js
const app = new Hull.App({ port, hostSecret, service });

const server = app.server();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entertains the confusion even more.
We are using app (which is still not an express app) and calling server to create an express app...
And then we're not calling it app but server, and we're not setting it up.

The reason we want to set it up:

  • we want control over the port

  • we might want to add middlewares before and after hull does it's job

  • we want to be able to set the engines and static assets. Hull shouldn't force and own this. it's "good" boilerplate.

  • we can't setup an express router here unfortunately since routers can't have engines. router.set isn't supported

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want to add middlewares before and after hull does it's job

This is still the hardest point of the architecture. Before we had the handlers/routers a blackboxes with all middlewares being applied (https://github.com/hull/hull-node/blob/master/src/notif-handler.js#L173), without the option to inject some custom logic between selected middlewares. Right now, they are split into a app level and router level, this allows for some degree of customization, but still not perfect.

I will come up with some new examples here too.

const worker = app.worker();

worker.attach({
customJob: (req) => { process }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attach isn't very clear. should add docs on how it works

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that attach might not be the right verb here.
Why not use ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, I will switch the naming here.

docs/CONTEXT.md Outdated
[Hull API client](../README.md) initialized to work with current organization

#### ship
Ship object with manifest information and `private_settings`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs some example data

```js
import Hull from "hull";

app.use(Hull.Middleware({ hostSecret: "supersecret" }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here, what is app. is it an express app or a hull app?
It seems here it's express - example of confusion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's my paragraph here ;P

- If the configuration or the secret is invalid, an error will be thrown that you can catch using express error handlers.

```js
app.use(function(req, res, next){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a very important one.

Developer MUST be allowed to define his own middleware in the middle of the stack to define how the Token will be retrieved and put in the right place (req.hull.token) or the right keys (req.hull.config)


app.use((req, res) => { res.json({ message: "thanks" }); });

app.use(function(err, res, req, next){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. this MUST be something the developer can do. a generic error catching middleware to avoid crashes where he can decide what to return

Cant' take this away from them


## serviceMiddleware
This is a middleware which helps to inject the custom ship modules to the request object.
Thanks to that, all the custom functions and classes are automatically bind to the context object:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"bound"

app.post("/get-segments", (req, res) => {
const { service } = req.hull;

service.getSegmentIds({ limit: 500 })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do this. I feel

import getSegmentIds from "get-segment-ids"
...
getSegmentIds({limit: 500, context: req.hull })

is just as simple and has even less side-effects.
What's the benefit here?

@michaloo michaloo changed the base branch from master to release/0.11 February 28, 2017 13:58
@michaloo michaloo merged commit 455a79d into release/0.11 Feb 28, 2017
@michaloo michaloo deleted the feature/ship-utilities branch February 28, 2017 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants