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

Decouple base library from svelte #21

Open
guillemcordoba opened this issue Apr 18, 2021 · 10 comments
Open

Decouple base library from svelte #21

guillemcordoba opened this issue Apr 18, 2021 · 10 comments

Comments

@guillemcordoba
Copy link
Collaborator

I see the syn.js logic using svelte stores, which is fine for this UI, but won't be usable for other UIs built on top of this library and with any other framework.

I suggest using MobX, a js-only updatable and reactive store which has impressed me a lot, what do you think @zippy @qubist ? If you agree, I will gladly do the refactor, but only if you feel comfortable switching to that :)

@zippy
Copy link
Member

zippy commented Apr 19, 2021

Yes, happy to switch. This was a fast experiment to get things rolling. Much happier to have a UI framework independent solution. Not sure how that looks inside the Svelte UI though. Looks like there's https://npm.io/package/svelte-mobx-observer and https://github.com/xelaok/svelte-mobx. Thoughts?

@guillemcordoba
Copy link
Collaborator Author

Yes exactly, mobx is a bit like redux, but without the boilerplate with actions and so on. Then you have integrations with all major frameworks and tools, the ones you pointed out should work out of the box for svelte.

I like it quite a bit, here is an example store that re-renders the components any time the state changes: https://github.com/holochain-open-dev/profiles/blob/main/ui/src/profiles.store.ts

I think it makes for very simple but flexible state-management mechanism.

@qubist
Copy link
Collaborator

qubist commented Apr 19, 2021

Looks great to me! The Svelte store was definitely hard to wrap our heads around when hacking out syn, so I don't feel too fond of it to swap it out for something more extensible

@btakita
Copy link
Contributor

btakita commented Apr 22, 2021

Svelte stores are actually very extensible & independent of svelte components. It may even be useful to extract svelte/store into it's own library. Svelte stores supports a simpler functional reactive solution (think RXjs lite). If you are willing to stick this out a bit, I think you will find Svelte stores to be on par with or even superior to MobX & just as easy to interface with.

The paradigm is different, being each store represents state that is used across the app. Each piece of data is isolated. With a consistent naming convention for each abstraction, an data flow framework can be made based solely on consistent domain terminology & systematized naming idioms (similar to the naming conventions of Holochain's validate functions).

I have used svelte stores for the past few years, & they can be used with other frameworks, in a similar way that RXjs can be used with multiple frameworks. You can create a functional reactive graph of data using the derived stores, use the subscribe for event handler. I think Svelte stores are superior to RXjs, as it's easier to work with async/await, which makes the program flow more intuitive, IMO.

One pattern that I have found to be useful is to lazily instantiate the stores. I use a _be function, which will lazily instantiate each store onto a ctx object. The ctx object forms the basis to share data across the entire execution context, whether it be the UI, the web server, or a chain of functions. The nice thing about using a ctx object with lazily instantiated stores is that you only need to pass around (via dependency injection) one piece of state, the ctx object.

Svelte also supports getContext and setContext, so the ctx can be implicitly set at the top of the render tree & utilized in any child component.

I would also be happy to refactor the codebase to utilize these patterns, which I have used in a number of apps over the past few years. I think it's somewhat novel but very powerful & will be a simpler solution to any of the other alternatives, as the incidental complexity of passing state around is largely removed. Also, each node in the functional reactive graph is it's own autonomous function (via lazy instantiation), so data flow logic is cleanly separated into distinct files.

Another thing to consider, pnpm monorepos are a powerful way to separate libraries (npm packages) & apps in a single repository. A unifying paradigm, such as ctx object(s) & a composable naming system is effective at unifying data flow across multiple libraries & layers in a software stack.

@guillemcordoba
Copy link
Collaborator Author

Mmm okey those are good points, don't know that much about svelte stores. What I would do is be very careful about the assumptions that are baked in the core syn library, not so much the svelte UI built on top of it. I wouldn't want for the core library to use context to share state around, because I want to be able to build reusable custom elements that don't need any additional setup to work (I'm doing all my stuff with Lit and would want to be able to include the core syn library).

Have you used MobX, and if so, how does it compare to svelte stores?

@btakita
Copy link
Contributor

btakita commented Apr 22, 2021

I evaluated MobX a couple of years ago. I don't doubt that it does everything you are saying it does. My stipulation is that Svelte Stores are capable of providing a good api which can be used in a general sense. Svelte Stores are simple observable container objects which can be decorated with additional functionality. Think of a Store as a spreadsheet "cell", which can other "cells" depend on it. An update to a "cell" will propagate to the other "cells".

Since Svelte Stores are simple objects, just about any tried-and-true pattern of API development is on the table. IMO, the main difference between a Svelte Store & MobX Stores is how it's used. Svelte Stores are atomic "cells" holding one piece of mutable state while MobX Stores are "frames" holding a set of mutable state. Svelte Stores can be composed into a "frame" as well. Note that this analysis is on the idioms of both libraries.

Right now, the Stores in Syn are Singletons. Stores do not need to be Singletons, though it's useful for the Store to already be instantiated so the dependent functions can observe the Stores, which is why I like to lazily instantiate Stores & other state on a ctx using self-contained _be (think "to be or not to be") functions (implementation https://github.com/ctx-core/object/blob/master/src/_be.ts). I have also implemented _rc_be to reference count to clean up a Be once it's no longer used.

The ctx can be completely internal to the core library without additional setup by the dependent or even the knowledge that a ctx is being used internally by the dependent library. It's mainly a dependency injection & repository pattern which will remove the accidental complexity of instantiating & passing around instances in the Constructor of a chain of objects. It will remove a large portion of the complexity & coupling in the Syn domain logic. I'm working on a quick spike to refactor using ctx and Stores to demonstrate.

@guillemcordoba
Copy link
Collaborator Author

Mmm but what if I don't want to use the context pattern to build UIs on top of the core library? I mean, it's cool that we have some UIs that work that way, but we should be able to decouple the base business logic from that no? There are other ways to do dependency injection, and a lot of frameworks like angular or vue handle that in their own way.

About svelte stores, I see... I would be inclined to use mobx either way, it feels more natural here because you don't want individual cells of state to be independent, you want the whole state to react to changes with business logic like you have inside the Session and Syn classes, and have upstream components react to that. But I'm open also to trying it out with svelte's stores.

@zippy @qubist what do you think?

@qubist
Copy link
Collaborator

qubist commented Apr 22, 2021

I'm quite over my head with this conversation

@btakita
Copy link
Contributor

btakita commented Apr 23, 2021

@guillemcordoba If you don't want to use the context pattern, don't use it. Since svelte has getContext & setContext, you don't need to use or pass along the ctx at all in a component. I just find that ctx with lazy loading creates a declarative dependency graph, instead of temporal coupling & keeping track of unique objects to pass around n levels deep caused by dependency instantiation & injection; resulting in a more simple & decoupled codebase that is easier to reason about & work with. The "cell" approach composes into more complex "cells", removing the up front design concern about how to group state or functions together in an object. Instead you would have a bunch of "cells" which can be composed together. You can create a derived "cell" with exactly the state that you need. You can create controller objects which compose shared "cells" or it's own "cells" & methods to support the view. The Session & Syn classes can be decomposed into related pieces based purely on a domain considerations. Circular dependencies (e.g. between Session & Syn) can be easily unrolled. I find that classes are often designed for imperative convenience, often to avoid passing around objects, but leading to incidental/accidental complexity. With ctx, the object passing mostly goes away by design. I'll be able to get around to coding this very soon. I think you will like the result.

@qubist No worries, I'll have some code soon.

@dukejones
Copy link

Word on the street is Zustand is The Way -- I'm using it in my latest project and it's great and simple. Also I hear it works well with a lot of processing such as in concert with threejs.

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

No branches or pull requests

5 participants