angular-ui / AngularJS-StyleGuide

Example of scalable architecture for my NG-Conf 2014 talk
http://www.youtube.com/watch?v=Iw-3qgG_ipU
284 stars 49 forks source link

Further information and advice #1

Closed thomasstevens89 closed 10 years ago

thomasstevens89 commented 10 years ago

Hi Dean. I watched your presentation on Youtube from ngconf 2014 and was fascinated by your approach to data objects and event streams, etc...

I'm currently building a large-scale angularJS app and have had a lot of issues keeping data objects in sync across the app as well as to the backend. I'd love to learn more about using baconJS for CRUD operations and I'm particularly interested how it could be useful in a realtime app using websockets (pubnub, socket.io, etc...)

Could you point me to any examples of your approach to data objects being used in an angular app? Or perhaps suggest articles I could look at to help me understand better? Thanks for any help you can provide.

thomasstevens89 commented 10 years ago

One other question I had was whether you suggest any particular angular boilerplate system? ng-Kickstart, yeoman, etc...?

ProLoser commented 10 years ago

I started this repo because of those sorts of requests. And no, I don't have any recommended boilerplate. I sort of hate all of them. I am right night trying to get the yeoman-angular one working and I'm mixed-to-unimpressed. This repo MAY server (of sorts) as a sort of boilerplate eventually, but as it stands now I find myself far more often than not mixing up my own build for each app.

Feel free to help me flesh out this repo or point to places in the repo you would like me to expand upon. I'm not actively working on this project but figured anytime someone asks me for an example I'll add it here and figure that eventually it will become a fairly well-rounded demonstration of my talk.

ProLoser commented 10 years ago

I started to stub out some socket pseudo-code

take a look at modules/Objects.coffee and modules/Socket.coffee

thomasstevens89 commented 10 years ago

Thanks for that. I think I need to fully learn coffeescript and get used to it... that should get rid of half my confusion! I might have a go of re-creating the Todos example app using this ORM approach and see how far I get... then I can help you expand this repo hopefully.

ProLoser commented 10 years ago

@thomasstevens89 I understand not everyone understands Coffeescript, unfortunately I find it helps keep things concise and for the purposes of classes it makes life considerably easier than using libraries. I would use the cheat-sheet found in my presentation and using coffeescript.org to test out what snippets of code look like until you get the hang of it.

thomasstevens89 commented 10 years ago

http://blog.flowdock.com/2013/01/25/syncing-backbone-models-in-realtime-over-socketio/ This article has been very helpful. Although from what I understand, their approach is slightly different in that they use a single message bus and the objects use a filter to receive events that are relevant to them.

What is the advantage of using a queryStream as well as an eventStream?

ProLoser commented 10 years ago

The queryStream is an additional idea I had to make sending messages to the backend simpler, in addition to receiving messages from the backend. Additionally, if they do not leverage nested objects, then they are still getting a performance hit compared to my solution. By nesting the objects and filtering at every level of the stream, you are shaving off more and more events from the callback chain. This means objects really low on the chain will never even have to check events that were shaved off near the top. If their relationship model is flat, then every single object is going to perform a check on every single event, even if nothing is ever actually fired for 99% of those objects. This overhead is dramatically reduced in my implementation. It's similar to having thousands of watcher expressions firing without their callbacks needing to be fired.

Anyway, WHY THE QUERYSTREAM:

Normally, if you want to send a request to the backend, such as 'create task', you would have to pass data such as projectId or userId or other stuff like that. All of these properties are not actually specific to the task object, and they clutter up the scope of knowledge/concern for the task object. They also require you to collect all this information from all over the place and manually pass it through when calling the endpoint. We used to have stuff like this stored in a global context object or on the state like $scope.currentProject or $scope.currentUser, etc. But this became cumbersome and caused lots of circular dependency race-condition nonsense.

By using a queryStream, instead of firing off a query directly from the TaskObject, you push a new query onto the stream, with all the data regarding the taskObjectInstance only. Then the parent object (projectObject) sees this event, and decorates more context, such as projectId, onto the payload. Then the userObject sees the event and decorates the userId context. Finally when the query reaches the end of the stream, it's fired off to the backend.

The point is that every object is decorating the query about himself, and never has to worry about any other objects (or context). Does that make sense?

thomasstevens89 commented 10 years ago

Ok yes makes perfect sense. We know where the 'end' of the stream is and when to send it to the backend because all objects extend the AppObject?

ProLoser commented 10 years ago

All object classes extend the BaseObject class, however, all object instances are descendants of the AppObjectInstance, yes.

Yes, it can be a little confusing. One is simply class inheritence, the other is object inheritance. Class inheritance simply allows for DRY code and uniform API, but isn't really necessary or anything.

ProLoser commented 10 years ago

Checkout these 2 lines: https://github.com/ProLoser/AngularJS-ORM/blob/master/modules/Objects.coffee#L61-L62

This is where the 'end' of the streams are (meaning they go from the ORM objects into the Socket)

ProLoser commented 10 years ago

And this is where the streams are created and tied to ACTUAL socket methods: https://github.com/ProLoser/AngularJS-ORM/blob/master/modules/Socket.coffee#L10-L20

thomasstevens89 commented 10 years ago

Ok now that makes sense too :) thanks

How is the relationship between parent objects and their descendant object instances maintained or referenced? Is it because higher level objects always receive the upstream query payloads of their children?

For example how do you tell a taskObject instance that its parent is a projectObject?

ProLoser commented 10 years ago

Okay it looks like I didn't have any examples of that stuff yet, so I went ahead and made some examples.

Checkout the new ProjectObject: https://github.com/ProLoser/AngularJS-ORM/blob/master/modules/Project/Project.coffee#L46-L91 and the latest version of the AppObject: https://github.com/ProLoser/AngularJS-ORM/blob/master/modules/Objects.coffee#L66-L100

ProLoser commented 10 years ago

Also, you DONT actually tell a taskObject that it's parent is some projectObject. We USED to think we had to, but we found that with this new structure, that never became necessary anymore. That's part of what the upStream helps alleviate.

ProLoser commented 10 years ago

Here is a concrete example of the upStream in use: https://github.com/ProLoser/AngularJS-ORM/blob/master/modules/Project/Project.coffee#L61-L79

Note that we didn't have to pass in the projectId explicitly when we called this query because it gets decorated automatically. You can also imagine that in the taskObject, when we fire off a query the projectId will also be decorated there too.

thomasstevens89 commented 10 years ago

This is all extremely helpful - thankyou so much. I'll spend some time experimenting and see how I go.

ProLoser commented 10 years ago

Feel free to open more issues or pull requests if you have ideas/questions/suggestions. This is all a WIP and should be taken with a grain of salt as all of this is pseudo-code and theoretical programming off the top of my head lol.