untangled

NEW CHANNEL: #fulcro
tony.kay 2016-11-21T13:58:30.004656Z

@kauko Time travel in Om is made possible by the fact that you’re using immutable state and structural sharing (see https://en.wikipedia.org/wiki/Persistent_data_structure). There is simply a vector of 100 (by default) past snapshots of app state. 99% of that state is structurally shared, so it doesn’t really consume much extra space.

tony.kay 2016-11-21T13:58:46.004657Z

state -> state2 -> state3 -> …

tony.kay 2016-11-21T13:59:32.004658Z

Ideally, rendering is a pure function of state. Therefore (render state2) simply updates the DOM to look like it did at that state

tony.kay 2016-11-21T13:59:49.004659Z

time travel in this model is Trivial with a capital T

tony.kay 2016-11-21T14:00:29.004660Z

The “diff” that @adambros spoke of is generated by comparing two points in this sequence of snapshots. You can generate a delta between any two steps with, e.g. https://clojuredocs.org/clojure.data/diff

tony.kay 2016-11-21T14:03:06.004663Z

as for your more specific question, @kaoko, "how do I tell Om Next not to store (and replay) async things, and only store the things that actually mutate my state?"

tony.kay 2016-11-21T14:05:17.004664Z

The reason you probably didn’t get much response is this doesn’t make a lot of sense in the Om Next model. In Om next, async is meant to be hidden behind the scenes. Only the networking code need ever directly deal with it, and that is hidden behind an abstraction (transactions). There is no way to “store” an async thing…async has to do with how things are done, not what is stored.

tony.kay 2016-11-21T14:05:47.004665Z

And since you never put code in your app state, it makes little sense to talk about “replaying async things"

tony.kay 2016-11-21T14:07:25.004666Z

the state history is simply that: a history of snapshots of the application (data) state. If you wanted to store some record of “what I just did to get from state1 to state2” then you’d have to explicitly encode that (say, as data under a :last-mutation key or something of your invention). In other words, every one of your mutations would need to be saving “I ran” in app state somewhere. Om Next does not do that.

tony.kay 2016-11-21T14:10:54.004668Z

In stock Om Next you could add this encoding into a wrapper around all mutations (e.g. a function that composes the result of a multimethod with your recording). In Untangled the easiest spot at the moment would be via the new :mutation-merge hook when creating the client (though all you see there is the mutation name and return value from the server).

tony.kay 2016-11-21T14:12:14.004669Z

I’ve been planning to do some kind of history viewer transition recording of this sort as part of Untangled, but have not gotten to it yet. I’d like to have some top-level Untangled keys that represent the “thing that happened to go from state1 to state2” for debugging purposes, and would include the status code from the server along with a server-based timestamp if possible.

tony.kay 2016-11-21T14:12:41.004670Z

Then when you’re using the support viewer you'd have an easy time correlating production logs with user-reported errors

kauko 2016-11-21T14:27:52.004673Z

Thanks for the explanation @tony.kay !

kauko 2016-11-21T14:28:51.004674Z

I'm used to thinking about time travel as an ordered log of events, which is a different (and seems to me, a better) way of achieving time travel

kauko 2016-11-21T14:29:24.004675Z

Storing the states, or just how they changed, certainly works, but it doesn't show me what caused that change, right?

kauko 2016-11-21T14:30:16.004676Z

What I'm talking about is basically CQRS and Event Sourcing, you just need those "async commands" and "sync commands" when you're in the client. Pretty much what Elm does with Effects and Actions.

tony.kay 2016-11-21T14:32:11.004677Z

the transaction is the event, and you can save it.

tony.kay 2016-11-21T14:32:38.004678Z

CQRS is trivial and quite compatible with the entire model. In fact a talk was given last year…let me see...

tony.kay 2016-11-21T14:32:54.004679Z

https://www.youtube.com/watch?v=qDNPQo9UmJA

kauko 2016-11-21T14:33:06.004680Z

Yeah, I know it's trivial 🙂 That's partly why I was surprised to find no mention of anything related to it

kauko 2016-11-21T14:33:50.004681Z

This is the talk that really made me understand Event Sourcing better http://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/

tony.kay 2016-11-21T14:34:54.004683Z

Yeah, the community is quite aware of how good a fit it is, but Om Next isn’t specifically targeted at CQRS. It just happens to fit nicely.

tony.kay 2016-11-21T14:35:51.004684Z

we’re aware of it as well. It adds a bit of complexity to an average or beginning app that probably isn’t worth it, so talking it up as the thing to do isn’t of much interest.

tony.kay 2016-11-21T14:36:41.004685Z

Setting up your event recording, your processing pipeline, etc etc etc is a lot of work, admin overhead, and added complexity for new devs. You have to balance that with the overall benefits I think.

tony.kay 2016-11-21T14:37:13.004686Z

But yes, Om Next is a superb fit if you want to use that model for your stack

kauko 2016-11-21T14:41:03.004687Z

That's good to hear 🙂 So Om Next has its own model, but if I want to have Event Sourcing, I can implement it myself. I guess since Om Next's time travel doesn't depend on the log of events, you don't really have to even care about the whole Effect/Action thing either.

kauko 2016-11-21T14:42:20.004688Z

I honestly just feel like you can gain so much value from Event Sourcing. Not only can the debugger be helpful during development, but getting a full history from you client in a bug report is awesome... though this you can get just by replaying the states too.

kauko 2016-11-21T14:42:47.004689Z

Where the log based approach wins out, is in writing tests

kauko 2016-11-21T14:43:31.004690Z

Maybe you can write unit tests that say "After these effects, the state must look like this".

tony.kay 2016-11-21T15:13:42.004691Z

@kauko our testing support already has direct support for that (see protocol testing)

tony.kay 2016-11-21T15:14:10.004692Z

that’s really all an Om Next mutation is, after all: an event that causes state -> state’

tony.kay 2016-11-21T15:14:48.004693Z

no need for anything to be log-based. It’s the “event” (in Om Next terminology: transaction) that matters

kauko 2016-11-21T16:02:14.004694Z

Hmm, sure! The log of events and a log of state transitions both represent exactly the same thing, but are not events easier for humans to read? Like you said, it is possible to store the transactions somewhere (I assume you'd give them nice names), but then you are doing Event Sourcing

tony.kay 2016-11-21T16:03:28.004695Z

Not sure why you’re differentiating. In Om Next the transactions are the abstraction that you’d refer to as an event. They are the data representation of what to do. How they are implemented is hidden. Thus, they are isomorphic to the events of event sourcing.

kauko 2016-11-21T16:04:13.004696Z

maybe I'm misunderstanding something then 😄 text is not the easiest medium

tony.kay 2016-11-21T16:05:49.004697Z

in Om Next, you say something like (om/transact! this ‘[(app/do-something)]). That transaction is represented as EDN. Pure data. Thus the quoting. You’re not calling a function there (do-something)…you’re sending it off to the ether to be interpreted by the underpinnings. That bit of data is also what goes over the wire and could be stored (if you wanted) as the thing that causes the state transition in the new state itself.

tony.kay 2016-11-21T16:06:33.004698Z

so your new app state could be made to include it…it’s just a list with a symbol in it

tony.kay 2016-11-21T16:06:47.004699Z

{ :transition-reason ‘[(app/do-something)] …rest of app state...}

tony.kay 2016-11-21T16:07:41.004701Z

and since it is what goes over the wire, it is what you’d put in your CQRS log before executing it on the server

tony.kay 2016-11-21T16:07:42.004702Z

etc

kauko 2016-11-21T16:09:30.004703Z

Hmm, I wonder if what threw me off was when you said "no need for anything to be log-based". Do we understand the word "log" differently? What I meant by log, is just some place where transactions are stored in a time-ordered manner. New transactions go on top of the log.

kauko 2016-11-21T16:10:05.004704Z

I'm sorry if I'm being dumb. 🙂 Really appreciate your help!

tony.kay 2016-11-21T16:10:26.004705Z

that is exactly what I mean in the CQRS context

tony.kay 2016-11-21T16:11:34.004706Z

there is no need for log-based representation to get the benefit from testing is what I meant

tony.kay 2016-11-21T16:11:41.004707Z

of course you use a log IF you’re doing CQRS

tony.kay 2016-11-21T16:12:16.004708Z

I was just saying the two are not related in Om Next, because the transition (event/transaction as data) is a first-class citizen of the design. *If* you choose to log it and do all of the rest of the infrastructure around that, then you get CQRS as well

tony.kay 2016-11-21T16:13:25.004712Z

All of Om Next is: state ——transaction as data—> state'

tony.kay 2016-11-21T16:13:49.004713Z

thus, the unit-testability of state transitions (all operations on the UI or server) is a given…well, on the server those are usually integration tests since they side-effect

tony.kay 2016-11-21T16:14:59.004716Z

and the transaction as data does map to some specific code unit

kauko 2016-11-21T16:15:26.004717Z

Yeah, I think I understand what you're saying now. Kind of feel like we've been talking about the same thing this whole time. 😄

kauko 2016-11-21T16:17:00.004718Z

What I meant by the logs + unit tests was mostly how cool it would be to get an error report from a client (that you can replay) - fix the problem - and finally use that log of transactions in a test.

kauko 2016-11-21T16:18:56.004719Z

BUT let's not get stuck with this. I've another question, if you don't mind answering. If I understood you correctly, you said that Om Next does not store the transactions themselves, but it stores the state transitions. Where is that data stored? Can I access it myself somehow?

tony.kay 2016-11-21T16:20:31.004720Z

it stores the prior states, yes

tony.kay 2016-11-21T16:21:11.004721Z

see from-history in the om next source code

tony.kay 2016-11-21T16:21:16.004722Z

next.cljc

tony.kay 2016-11-21T16:21:30.004723Z

near line 2820

tony.kay 2016-11-21T16:23:10.004724Z

it basically keeps an array of states and an index by UUID (the UUIDs are logged for debugging purposes)

kauko 2016-11-21T16:25:25.004725Z

Cool, thanks!

tony.kay 2016-11-21T16:25:35.004726Z

welcome

kauko 2016-11-21T16:26:02.004727Z

I feel like there's so much to Om Next that the docs in github don't mention at all 😕

tony.kay 2016-11-21T16:26:25.004728Z

you found docs?? 😜

kauko 2016-11-21T16:26:36.004729Z

which is a shame, because if non-clojurians knew what kind of ideas Om has come up with, even more people would come to Clojure!

kauko 2016-11-21T16:27:06.004730Z

lol yeah, the docs which STILL say they shouldn't be edited because they're WIP

tony.kay 2016-11-21T16:28:25.004731Z

well, that’s part of the objective of Untangled: Om Next leaves a lot to be defined. Make it more approachable. That said, we’re not pushing the fringe ideas very much. The core ideas are so cool that we feel like letting ppl get used to those first.

👍 1
tony.kay 2016-11-21T16:29:20.004732Z

Graph database, co-located queries, transactions as data, no async code needed in the application-layer, rendering as a pure function, no in-place mutation….it’s a lot of change

kauko 2016-11-21T16:33:22.004735Z

Spec has a lot of potential to shake things up even more. David already mentioned he has been thinking about if queries could be represented just as specs. I don't really understand what the effect of that would be, but during the weekend I got stuck with a similar idea.. wouldn't it be cool if we could somehow define specs for when a transaction is legal? Wouldn't that mean we could use generative testing to "use" our application in crazy ways, to see if it ends up in an illegal state? 😄

kauko 2016-11-21T16:33:38.004736Z

Not sure how it would look like, but that would be amazing! 😄

tony.kay 2016-11-21T19:48:01.004740Z

I just fixed up the lein i18n plugin and released docs on the devguide. It covers modules to some extent as well (e.g. dynamically loading translations). Not all tested, but should be close.

tony.kay 2016-11-21T19:48:41.004742Z

https://github.com/untangled-web/untangled-lein-i18n

tony.kay 2016-11-21T19:49:23.004744Z

I left a ton of escaped quotes in the README…but you get the idea 🙂

berkeleytrue 2016-11-21T20:30:20.004746Z

Hello Everyone!

🦜 1
berkeleytrue 2016-11-21T21:34:03.004747Z

I’m having a hard time understanding how to implement the static initial-state method. My child components run fine and their dom elements render, but their initial state does not show up in the app state

berkeleytrue 2016-11-21T21:34:41.004750Z

But the main bit is

(defui ^:once Header
  static uc/InitialAppState
  (initial-state [this params] {:title "New Todo" :value "foo"})

  static om/IQuery
  (query [this] [:ui/value :title])
  static om/Ident
  (ident [this {:keys [title]}] [:header/by-title title])

  Object
  (render [this]
          (let [{:keys [value] :or {value ""}} (om/props this)]
            (dom/header
              #js {:className "header"}
              (dom/h1
                #js {}
                "Todo")
              (dom/input
                #js {
                     :className "new-todo"
                     :placeholder "What needs to be done?"
                     :value value
                     :onChange (fn [e] (m/set-string! this :value :event e))
                     })))))

(def ui-header (om/factory Header))

berkeleytrue 2016-11-21T21:37:02.004753Z

This seems to be how most tutorials online define intial state, but :title and :ui/value are both undefined with ident/render

adambros 2016-11-21T21:37:18.004754Z

are you composing initial-state from the root of your ui?

berkeleytrue 2016-11-21T21:37:37.004756Z

I believe so

adambros 2016-11-21T21:38:17.004759Z

ah no you are not, line 37

adambros 2016-11-21T21:38:27.004760Z

so initial-state needs to be composed like queries

berkeleytrue 2016-11-21T21:38:48.004761Z

Hmm, I’m not sure what that means.

adambros 2016-11-21T21:39:11.004762Z

so you need to call Header‘s initial-state method in your Root initial-state

berkeleytrue 2016-11-21T21:39:17.004763Z

I see

adambros 2016-11-21T21:39:38.004764Z

yeah, otherwise untangled wont ever use your Header‘s initial-state

adambros 2016-11-21T21:39:44.004765Z

all you’ve done is defined it

berkeleytrue 2016-11-21T21:40:00.004766Z

Ok sweet. I think I understand now.

adambros 2016-11-21T21:40:09.004767Z

the only “special” initial-state is root, and thats because you define it as your root in new-untangled-client

adambros 2016-11-21T21:40:17.004768Z

so untangled knows about that one

adambros 2016-11-21T21:41:03.004769Z

err not new-untangled-client, rather mount

berkeleytrue 2016-11-21T21:44:34.004774Z

Sweet! Thank worked. Thanks so much @adambros

😄 1
berkeleytrue 2016-11-21T21:53:01.004775Z

@adambros Is the same true for queries? Should they be composed together?

berkeleytrue 2016-11-21T21:59:58.004776Z

Yes, just found it in the untangled guide. 🙂