architecture

soxley 2024-07-15T14:47:03.652899Z

Is https://github.com/stuartsierra/component a recommended architecture for ClojureScript applications? Are there any new trends in how to structure an application? Are there any special considerations for ClojureScript as opposed to Clojure?

Rupert (Sevva/All Street) 2024-07-19T08:00:25.724699Z

Frontends can be extremely big and complex codebases now. So if dependency injection is a solution on the backend then I can't see why it shouldn't also help on the frontend? Dependency Injection allows from (1) great modularization (2) gluing things together at runtime (so you don't need to recompile for some changes). e.g. The frontend can may need a translation component - there are two implemtations written and with dependency injection you can switch between them without recompiling code.

Drew Verlee 2024-07-19T12:13:22.007569Z

@rupert I simply haven't seen it on the front end. Have you? If so, can you share what was specifically in the components? Maybe DI would be more useful in ways i didn't understand.

Rupert (Sevva/All Street) 2024-07-19T13:37:10.509819Z

I’ve used it extensively on the Frontend at work with UIX (react wrapper). Probably had hundreds of components being wired together by DI. Allowed us to have multiple versions of the same interface (eg regionalization and different color themes etc). DI doesn’t just do state- it also wires in all your config/settings etc.

Drew Verlee 2024-07-19T13:48:06.658219Z

@rupert If you gave me a specific use case, i might understand. Maybe using shadow-cljs is taking care of a lot of reloading woes? i'm not sure.

Rupert (Sevva/All Street) 2024-07-19T14:40:32.306709Z

Example: • Page component and menu component both need a translator component. • Currently ◦ we have 1 “page” component instance, 1 “menu” component instance and 1 “Google translator” component instance and it receives an api key. • Later ◦ we decide to instantiate 3 “Google translator” component instances to be used by the “page” component and another 1 “Google translator component” and one new “Yandex” translator component - both to be used by “menu” component. • These changes can all be done without touching/seeing/changing/recompiling the code to “page” component or “google translator” component or “menu” component and we didn’t have to design these components in a special way either. • No component is even aware of the component library (no dependency on it).

Drew Verlee 2024-07-19T15:08:29.980739Z

I see, thanks for the example! What would be the start and stop functions on the translator component?

soxley 2024-07-19T15:13:26.586399Z

To address @drewverlee’s earlier question, in my particular case I am using node for an AWS Lambda function that will connect to DynamoDB.

👍 1
👀 1
Rupert (Sevva/All Street) 2024-07-19T15:17:22.079139Z

Perhaps the start function fires up a web socket connection to the translator service using the supplied api key. Or potentially instantiation does nothing and is just used to receive the api key which is used for making REST requests when necessary.

👀 1
Piotr Roterski 2024-07-19T17:21:38.821479Z

@drewverlee shadow-cljs serves different purpose as it compiles js from clojurescript code but the production build does not contain shadow-cljs itself, while the DI libs would be used at runtime. I'd say, conceptually, Dependency Injection serves two purposes: - decoupling (so components don't need to know anything about other components or the system at large), - dependency resolution (so if there are any interdependencies between components, DI ensures that they are started in correct order). It's not a clojure-only idea, angular has always been bullish on explicit DI https://angular.dev/guide/di/dependency-injection while React sidesteps it with nested context providers and/or useSyncExternalStore hooks. What donut.system (or some other libs mentioned here) adds to the mix is lifecycle management in single map system definition for overview/inspection purposes. Admitably, this could be an overkill for the majority of webapps, as you can get far enough without even considering it but we've all seen our share of messy hairball codebases, that otherwise could benefit from such approach. Sure, it adds some overhead as the lib builds a DAG graph and then resolves it in reverse-topsort order, yet on frontend you mostly care about cutting down the render time so I wouldn't rush to use it for everything. However, I could think of few stateful components that would justify bringing it in, especially in rich-client offline-first apps: websocket/p2p connections, all sort of webworkers, client-side databases (e.g. duckdb), sandboxes, iframes (would that work?) or code-split modules (circling back to shadow-cljs https://shadow-cljs.github.io/docs/UsersGuide.html#CodeSplitting). Having said that, I haven't seen nor used donut.system on frontend yet, but I'm very tempted by the idea. The main issue for this use-case now is the lack of async support in cljs https://github.com/donut-party/system#async-signaling

Drew Verlee 2024-07-19T02:20:07.231899Z

@soxley i think of component as tree of stateful dependencies. e.g A component is where you describe what your server needs the database to be live before it can start. So i'm curious why you would need one for a clojure*script* application, which i assume is going to compile to javascript and will likely run in the brower. Unless of course your clojure compiles to node.

Rupert (Sevva/All Street) 2024-07-16T16:29:59.585699Z

Both https://github.com/weavejester/integrant and https://github.com/juxt/clip are compatible with ClojureScript if you want proper dependency injection.

dvingo 2024-07-17T01:23:34.005429Z

this talk references using integrant with re-frame https://www.youtube.com/watch?v=b_uum_iYShE

Drew Verlee 2024-07-20T22:27:57.404299Z

Thanks Piotr. I hadn't meant to imply Component and Shadow overlapped, i was imagining a case where someone built aspects of how shadow helps with repl driven development using a DI system.

Piotr Roterski 2024-07-15T21:21:10.365949Z

> recommended that's subjective and context dependent but there're few alternatives or evolutions of this idea. This rationale doc for donut-system is a good read on the subject: https://github.com/donut-party/system/blob/main/docs/rationale.org and donut-system itself is my favorite implementation for this component/system/lifecycle idea.

soxley 2024-07-15T22:53:26.172689Z

Cool, that’s along the lines of what I was wondering.

Lambda/Sierra 2024-08-12T16:22:55.830519Z

Although I ported the https://github.com/stuartsierra/component to ClojureScript, I never found it to be good fit for ClojureScript applications in a browser.

Lambda/Sierra 2024-08-12T16:24:03.110189Z

One of the goals for the Component library was to support interactive REPL development of long-lived server applications with a lot of runtime state.

Lambda/Sierra 2024-08-12T16:24:49.932799Z

The ClojureScript+browser development cycle is very different and requires different tooling with different trade-offs.

Lambda/Sierra 2024-08-12T16:25:20.821609Z

I haven't done enough ClojureScript lately to make any recommendations, but there are lots of projects out there.