911 lines
24 KiB
Markdown
911 lines
24 KiB
Markdown
---
|
||
# try also 'default' to start simple
|
||
theme: default
|
||
# random image from a curated Unsplash collection by Anthony
|
||
# like them? see https://unsplash.com/collections/94734566/slidev
|
||
background: /galaxy.jpg
|
||
# some information about your slides (markdown enabled)
|
||
title: Datastar
|
||
# apply UnoCSS classes to the current slide
|
||
class: text-center
|
||
# https://sli.dev/features/drawing
|
||
drawings:
|
||
persist: false
|
||
# slide transition: https://sli.dev/guide/animations.html#slide-transitions
|
||
transition: slide-left
|
||
# enable MDC Syntax: https://sli.dev/features/mdc
|
||
mdc: true
|
||
---
|
||
|
||
# To The Stars with Datastar
|
||
|
||
An interstellar journey
|
||
|
||
|
||
|
||
<!--
|
||
The last comment block of each slide will be treated as slide notes. It will be visible and editable in Presenter Mode along with the slide. [Read more in the docs](https://sli.dev/guide/syntax.html#notes)
|
||
-->
|
||
|
||
---
|
||
background: /galaxy.jpg
|
||
---
|
||
|
||
|
||
<h2 v-click class="absolute top-4 left-1/2 -translate-x-1/2">Drake Formular</h2>
|
||
|
||
<div class="mt-20">
|
||
|
||
$$ {1|2|3|4|5|6|7|all}
|
||
\begin{aligned}
|
||
N &= R_* \cdot f_p \cdot n_e \cdot f_l \cdot f_i \cdot f_c \cdot L \\
|
||
R_* &= \text{rate of star formation} \\
|
||
f_p &= \text{fraction of stars with planets} \\
|
||
n_e &= \text{number of habitable planets per star} \\
|
||
f_l &= \text{fraction where life develops} \\
|
||
f_i &= \text{fraction where intelligent life evolves} \\
|
||
f_c &= \text{fraction that develops detectable technology} \\
|
||
L &= \text{length of time civilizations are detectable}
|
||
\end{aligned}
|
||
$$
|
||
|
||
</div>
|
||
|
||
---
|
||
class: text-center
|
||
---
|
||
|
||
<h2 v-click class="absolute top-4 left-1/2 -translate-x-1/2">The "Drake" Formular of Webtechnologies</h2>
|
||
|
||
<div class="mt-20">
|
||
|
||
$$ {1|all}
|
||
\begin{aligned}
|
||
N_w &= D_b \cdot L_b \cdot F_b \cdot P_t \cdot F_f \cdot S_m \cdot C_{ss} \cdot C_l \cdot H_p \\
|
||
\\
|
||
N_w &= \text{Total Possible Tech Stacks} \\
|
||
D_b &= \text{databases (PostgreSQL, MongoDB, MySQL...)} \\
|
||
L_b &= \text{backend languages (Javascript, Python, Go...)} \\
|
||
F_b &= \text{backend frameworks (Express, Django, FastAPI...)} \\
|
||
P_t &= \text{transport protocols (REST, GraphQL, gRPC...)} \\
|
||
F_f &= \text{frontend frameworks (React, Vue, Svelte...)} \\
|
||
S_m &= \text{state management (Redux, Zustand, Pinia...)} \\
|
||
C_{ss} &= \text{CSS frameworks (Tailwind, UnoCSS, Bootstrap...)} \\
|
||
C_l &= \text{component libraries (shadcn, MUI, Ant Design...)} \\
|
||
H_p &= \text{hosting platforms (Vercel, AWS, VPS...)} \\
|
||
\end{aligned}
|
||
$$
|
||
|
||
</div>
|
||
---
|
||
class: default
|
||
---
|
||
|
||
<h2 class="absolute top-4 left-1/2 -translate-x-1/2">The Space of Webtechnologies</h2>
|
||
|
||
<div class="mt-20">
|
||
|
||
$$
|
||
\begin{aligned}
|
||
N_w &= ( D_b , L_b , F_b , P_t , F_f , S_m , C_{ss} , C_l , H_p ) \\
|
||
\\
|
||
\end{aligned}
|
||
$$
|
||
|
||
- Every website or web application is one star in this space.
|
||
- There are many combinations that work well. While others no so much.
|
||
- We all plot our path in this space. And have our current home there.
|
||
- There are clusters in this space, i.e. the React-Cluster, oder Angular or Vue.
|
||
- My current home is in the L-O-B with Go and Vue vicinity.
|
||
- There is an old Cluster called Hypermedia. Where all Webapps once lived.
|
||
- Hypermedia has developed a new bulge called HTMX.
|
||
- Next to it is a new tiny blob, called Datastar.
|
||
|
||
|
||
|
||
</div>
|
||
|
||
---
|
||
class: text-center
|
||
---
|
||
|
||
# My name is
|
||
# Thomas Hedeler
|
||
##
|
||
|
||
<v-click>
|
||
<p class="mt-20">
|
||
In the spirit of the late and great Douglas Adams, I call myself
|
||
</p>
|
||
|
||
<h1 class="mt-20">
|
||
A holistic developer
|
||
</h1>
|
||
</v-click>
|
||
|
||
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# My Motivation: Build a Minimal Viable Web App
|
||
##
|
||
|
||
<v-click>
|
||
<p>
|
||
It is not a compromise — this is a fully functioning web application, including database, application code, and deployment, implemented with the minimal number of lines of code.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
To embrace the <strong>Creativity Paradox</strong> and stay within our <strong>Complexity Budget</strong>, we apply a few constraints:
|
||
|
||
- Use as few third-party libraries as possible
|
||
- Avoid a front-end build step (no npm)
|
||
- Follow the principle of least power: “Favor simple, declarative solutions over complex code.”
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<div>
|
||
The benefits are clear:
|
||
|
||
- Less code → faster performance and fewer bugs
|
||
- Reduced technical debt → easier long-term maintenance
|
||
- Lower cognitive load → simpler to understand and extend
|
||
- Fewer dependencies → more robust and secure
|
||
- Leverage browser specifications and built-in capabilities
|
||
</div>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
In short: minimal code, maximal clarity, and a fully functional web app.
|
||
</p>
|
||
</v-click>
|
||
|
||
|
||
---
|
||
class: default
|
||
transition: fade-out
|
||
---
|
||
|
||
# How did I find Datastar? What is my motivation?
|
||
|
||
### Finding my combination of web technologies for a minimal viable web application.
|
||
|
||
<div class="mt-10"/>
|
||
|
||
- Part 1: The Database: SQLite
|
||
- Part 2: The No-ORM ORM - A very simple Data Abstraction Layer.
|
||
- Part 3: Developing a Web Server Application in Go.
|
||
- Part 4: Datastar - a lightweight framework for real-time collaborative web apps.
|
||
- Part 5: Modern HTML and modern CSS.
|
||
- Part 6: Web components.
|
||
- Part 7: Simple Deployments with a VPS, Nginx, Certbot and a single binary file.
|
||
|
||
---
|
||
class: default
|
||
transition: fade-out
|
||
---
|
||
|
||
# Part 1: SQLite:
|
||
### It is fast, feature complete* and rock solid.
|
||
<div class="mt-10"/>
|
||
|
||
## It is not SQ-Lite, it is SQL-ite
|
||
|
||
Since everybody knows SQLite, today just a few highlights:
|
||
|
||
- It has JSON and JSONB as built-in data types.
|
||
- It has 29 new functions to extract from JSON or to create JSON objects.
|
||
- It's CTEs make SQL Turing complete.
|
||
- The SQLite CLI can be used to execute "SQL-scripts". See demo.
|
||
|
||
\* It lacks features essential for a client-server environment, primarily multi-user concurrent write access,
|
||
built-in security and user management, and some advanced data types.
|
||
|
||
<div class="mt-40"/>
|
||
|
||
<div> * from my pov and for my needs and purposes</div>
|
||
---
|
||
class: default
|
||
transition: fade-out
|
||
---
|
||
|
||
# Part 2: A very simple Data Abstraction Layer:
|
||
|
||
<div class="mt-10"/>
|
||
|
||
Features:
|
||
- Simplified Database Lifecycle Management.
|
||
- A 'Record' Data Type for Generic Data Handling.
|
||
- High-Level CRUD Operations.
|
||
- Fluent Transaction API.
|
||
- Abstraction and Safety.
|
||
- Utility Functions.
|
||
|
||
<div class="mt-40"/>
|
||
---
|
||
class: default
|
||
transition: fade-out
|
||
---
|
||
|
||
# Part 3: Developing a Web Server Application in Go.
|
||
|
||
<div class="mt-10"/>
|
||
|
||
Why Go?
|
||
|
||
- Go is a compiled language that generates native machine code.
|
||
- Go's core strength is its built-in, lightweight concurrency model using goroutines and channels.
|
||
- Go has a small, well-defined specification and a deliberately simple syntax.
|
||
- The standard library is comprehensive, especially for web development.
|
||
- Go compiles into a single, static binary with no external dependencies.
|
||
- Go is simple, just 25 reserved words in the language.
|
||
- Can embed the database engine (modernc/sqlite)
|
||
- Can serve static code from embeded folders and files.
|
||
- Can embed other resources, like sql files or template files.
|
||
- Has a built-in templating engine.
|
||
---
|
||
layout: quote
|
||
transition: fade-out
|
||
---
|
||
|
||
# Part 4: Datastar
|
||
|
||
## Build reactive web apps that stand the test of time
|
||
Datastar is a lightweight framework for building everything from simple sites to real-time collaborative web apps.
|
||
|
||
<div class="mt-10"/>
|
||
|
||
Quote Gillian Delany:
|
||
|
||
> The problem is Datastar is actually a backend agnostic backend framework with a 10 Kb shim. There has never been anything like it in practice. So it is hard to explain.
|
||
|
||
|
||
|
||
---
|
||
class: default
|
||
---
|
||
<Words />
|
||
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Reducing the Network to a Function Call
|
||
|
||
```js
|
||
{
|
||
const { data, error } = await to(fetch("https://api.example.com/api/board"));
|
||
if (error) {
|
||
// handle error
|
||
return;
|
||
}
|
||
// handle data
|
||
}
|
||
{
|
||
const { data, error } = await to(fetch("https://api.example.com/api/users/12"));
|
||
if (error) {
|
||
// handle error
|
||
return;
|
||
}
|
||
// handle data
|
||
}
|
||
|
||
// the function that "unwraps" the promise:
|
||
export function to(promise: Promise<Response>) {
|
||
return promise
|
||
.then((response) => response.json())
|
||
.then((data) => ({ data, error: null }))
|
||
.catch((error) => ({ data: null, error }));
|
||
}
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# HTTP - Protocol
|
||
#
|
||
|
||
<div><span v-mark.circle.purple="5">POST</span> https://api.example.com<span v-mark.circle.orange="1">/api/users/search</span>?<span v-mark.circle.red="2">page=2&limit=10</span> HTTP/1.1</div>
|
||
<div v-mark.box.blue="3">
|
||
<div>Host: api.example.com</div>
|
||
<div>User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)</div>
|
||
<div>Accept: application/json</div>
|
||
<div>Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</div>
|
||
<div>Cookie: sessionId=abc123; theme=dark</div>
|
||
<div>Content-Length: 89</div>
|
||
</div>
|
||
|
||
<div mt-4 ></div>
|
||
<div v-mark.box.green="4">
|
||
<div>{</div>
|
||
<div>body: data for the search request</div>
|
||
<div>}</div>
|
||
</div>
|
||
|
||
---
|
||
class: default
|
||
---
|
||
# SSE - Server-Sent Events
|
||
#
|
||
|
||
<div><span v-mark.circle.purple="5">GET</span> https://api.example.com<span v-mark.circle.orange="1">/api/events/stream</span> HTTP/1.1</div>
|
||
<div v-mark.box.blue="2">
|
||
<div>Host: api.example.com</div>
|
||
<div>Accept: text/event-stream</div>
|
||
<div>Cache-Control: no-cache</div>
|
||
<div>Connection: keep-alive</div>
|
||
</div>
|
||
|
||
<div mt-4></div>
|
||
<div text-sm op-70>Response:</div>
|
||
<div v-mark.box.green="3">
|
||
<div>HTTP/1.1 200 OK</div>
|
||
<div>Content-Type: text/event-stream</div>
|
||
<div>Cache-Control: no-cache</div>
|
||
<div>Connection: keep-alive</div>
|
||
</div>
|
||
|
||
<div mt-4></div>
|
||
<div v-mark.box.yellow="4">
|
||
<div>event: message</div>
|
||
<div>data: {"user": "Alice", "action": "joined"}</div>
|
||
<div></div>
|
||
<div>event: update</div>
|
||
<div>data: {"count": 42}</div>
|
||
<div></div>
|
||
<div>data: Multi-line</div>
|
||
<div>data: message example</div>
|
||
<div></div>
|
||
</div>
|
||
|
||
|
||
---
|
||
layout: two-cols
|
||
layoutClass: gap-16
|
||
---
|
||
# HTTP Communication
|
||
#
|
||
```mermaid { scale: 0.6}
|
||
sequenceDiagram
|
||
participant Browser
|
||
participant Backend
|
||
|
||
Note over Browser,Backend: Traditional HTTP (Polling)
|
||
Browser->>Backend: GET /api/data
|
||
Backend-->>Browser: Response with data
|
||
Note over Browser: Wait...
|
||
Browser->>Backend: GET /api/data
|
||
Backend-->>Browser: Response with data
|
||
Note over Browser: Wait...
|
||
Browser->>Backend: GET /api/data
|
||
Backend-->>Browser: Response with data
|
||
```
|
||
::right::
|
||
# SSE Communication
|
||
#
|
||
```mermaid { scale: 0.6}
|
||
sequenceDiagram
|
||
participant Browser
|
||
participant Backend
|
||
|
||
Note over Browser,Backend: Server-Sent Events (SSE)
|
||
Browser->>Backend: GET /api/events (Accept: text/event-stream)
|
||
Backend-->>Browser: 200 OK (Connection open)
|
||
Note over Backend,Browser: Connection stays open
|
||
Backend-->>Browser: event: updatedata: {...}
|
||
Backend-->>Browser: event: updatedata: {...}
|
||
Backend-->>Browser: event: updatedata: {...}
|
||
Note over Browser,Backend: Real-time updates without new requests
|
||
```
|
||
---
|
||
layout: default
|
||
transition: fade-out
|
||
---
|
||
|
||
# Hypermedia
|
||
|
||
- The Representation is the Data: The representation (e.g., the HTML document) delivered from the server to the client contains all the data needed for that specific application state.
|
||
|
||
- Presentation and Links are Embedded: Crucially, not only is the raw data embedded, but also the presentation instructions (via CSS or HTML structure) and, most importantly for the workflow, the hypermedia controls (links and forms) that define the next possible state transitions.
|
||
|
||
- Hypermedia as the Engine of Application State (HATEOAS): This is the central feature of the workflow. The representations sent from the server must include hyperlinks and/or forms that indicate the valid next state transitions (the available actions) the client can take. The client does not use pre-compiled knowledge of the workflow; it is driven by the links provided in the current document.
|
||
|
||
|
||
---
|
||
layout: two-cols
|
||
layoutClass: gap-2
|
||
---
|
||
|
||
```mermaid { scale: 0.4}
|
||
graph TD
|
||
Start([Client Starts]) --> GetProducts[GET /products]
|
||
|
||
GetProducts --> ProductResp["Response: Product List<br/>links:<br/>add-to-cart: /cart/items<br/>self: /products"]
|
||
|
||
ProductResp --> AddCart[POST /cart/items]
|
||
|
||
AddCart --> CartResp["Response: Cart Updated<br/>links:<br/>self: /cart<br/>update-item: /cart/items/id<br/>remove-item: /cart/items/id<br/>checkout: /checkout"]
|
||
|
||
CartResp --> Decision1{Client Action}
|
||
Decision1 -->|Continue Shopping| GetProducts
|
||
Decision1 -->|Proceed| Checkout[POST /checkout]
|
||
|
||
Checkout --> CheckoutResp["Response: Checkout Session<br/>links:<br/>payment: /payment<br/>cancel: /cart"]
|
||
|
||
|
||
```
|
||
::right::
|
||
```mermaid { scale: 0.4}
|
||
graph TD
|
||
CheckoutResp["Response: Checkout Session<br/>links:<br/>payment: /payment<br/>cancel: /cart"] --> Payment[POST /payment]
|
||
|
||
Payment --> PaymentResp["Response: Payment Success<br/>links:<br/>order: /orders/id<br/>track: /orders/id/tracking<br/>invoice: /orders/id/invoice"]
|
||
|
||
PaymentResp --> GetOrder[GET /orders/id]
|
||
|
||
GetOrder --> OrderState{Order State}
|
||
|
||
OrderState -->|Pending| PendingResp["Response: Order Pending<br/>links:<br/>self: /orders/id<br/>cancel: /orders/id/cancel<br/>track: /orders/id/tracking"]
|
||
|
||
OrderState -->|Shipped| ShippedResp["Response: Order Shipped<br/>links:<br/>self: /orders/id<br/>track: /orders/id/tracking<br/>return: /orders/id/return"]
|
||
|
||
OrderState -->|Delivered| DeliveredResp["Response: Order Delivered<br/>links:<br/>self: /orders/id<br/>return: /orders/id/return<br/>review: /orders/id/review"]
|
||
|
||
PendingResp --> End([Workflow Complete])
|
||
ShippedResp --> End
|
||
DeliveredResp --> End
|
||
|
||
|
||
```
|
||
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Learn Some Templating System:
|
||
<div class="mt-10"/>
|
||
|
||
| Concept | Example | Meaning |
|
||
| ----------------- | ---------------------------------------- | -------------------------- |
|
||
| **Interpolation** | `{{ name }}` | Insert value of `name` |
|
||
| **Loop** | `{% for item in items %}...{% endfor %}` | Repeat block for each item |
|
||
| **Condition** | `{% if logged_in %}Welcome{% endif %}` | Conditional rendering |
|
||
| **Include** | `{% include 'header.html' %}` | Reuse a subtemplate |
|
||
| **Escaping** | `{{{ raw_html }}}` or `{{& raw_html}}` | Control HTML escaping |
|
||
|
||
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Templating and Hypermedia:
|
||
<div class="mt-10"/>
|
||
|
||
| | Templating | Hypermedia as the Engine of Application State |
|
||
| --------- | ------------------------ | ------------------------------------------ |
|
||
| Input | Data | Data + available transitions |
|
||
| Output | Document (HTML) | Document representing a state with actions |
|
||
| Function | Bind data to structure | Bind state transitions to structure |
|
||
| Goal | Present information | Drive navigation and state evolution |
|
||
| Mechanism | Placeholder substitution | Link/form embedding |
|
||
| Example | `{{.Title}}` → “Article” | `<a href="{{.Links.Edit}}">Edit</a>` |
|
||
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Putting Everything Together
|
||
##
|
||
|
||
<v-click>
|
||
<p>
|
||
At the core, the <strong>backend holds the application state</strong> — typically in a database.
|
||
All decisions about data, relationships, and transitions originate here.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
The backend generates pages by combining <strong>data (state)</strong>, <strong>presentation</strong>, and <strong>transitions (actions)</strong> using a templating engine.
|
||
This approach follows <strong>HATEOAS</strong>, a hypermedia-driven design where each page contains links and instructions describing valid next steps.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
The backend can also actively drive the frontend using <strong>Server-Sent Events (SSE)</strong>.
|
||
SSE allows updates to be pushed to the client in real-time, keeping the application synchronized without full page reloads.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
On the frontend, <strong>Datastar</strong> enables fine-grained interactivity and incremental page updates.
|
||
Instead of replacing entire pages like simple hypermedia, Datastar updates only the elements affected by the state changes, keeping the interface responsive and dynamic.
|
||
</p>
|
||
</v-click>
|
||
|
||
|
||
---
|
||
class: default
|
||
clicks: 7
|
||
---
|
||
|
||
# The Name "Datastar"
|
||
|
||
<v-click>
|
||
|
||
## Comes from the custom data-* Attribute
|
||
|
||
</v-click>
|
||
|
||
<v-click>
|
||
A custom data- attribute is an HTML attribute that allows you to store extra information about an HTML element in a standard, private way.
|
||
|
||
It must be named starting with the prefix data-, for example: data-product-id="123".
|
||
</v-click>
|
||
|
||
<p v-click="3">
|
||
<code>
|
||
<div id="foo" data-<span v-mark.circle.orange="4">what-ever</span>= <span v-mark.circle.red="5">"Any String you want"</span> > Lorem ... </div>
|
||
</code>
|
||
</p>
|
||
|
||
<v-click at="6">
|
||
there even is a javascript api for this:
|
||
```js
|
||
const el = document.getElementById("foo");
|
||
el.dataset.whatEver = "you've got it!";
|
||
```
|
||
</v-click>
|
||
|
||
<v-click at="7" >
|
||
|
||
- Every HTML element provides a key–value store (via its .dataset property) for arbitrary data you want to associate with that element.
|
||
- data-* attributes are real, queryable, and stylable DOM attributes. You can select them in CSS, query them in JS, and even animate style changes as their values change.
|
||
|
||
</v-click>
|
||
|
||
---
|
||
class: default
|
||
clicks: 5
|
||
---
|
||
|
||
# Datastar and `data-*` Attributes
|
||
##
|
||
|
||
<v-click>
|
||
<p>
|
||
The <strong>Datastar</strong> library builds on a simple, native web standard: custom <code>data-*</code> attributes.
|
||
Each element can carry its own key–value store of information, making HTML itself the natural host for behavior and state.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
Datastar defines <strong>21 <code>data-*</code> attributes</strong> that express intent directly in markup.
|
||
These attributes describe data flow, user interactions, and state transitions without writing imperative JavaScript.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
The <em>values</em> of those attributes form a small, declarative <strong>DSL</strong> — called <em>Datastar Expressions</em>.
|
||
Each expression describes what should happen when data or events change, turning attributes into a layer of reactive logic.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
By keeping logic inside the elements, Datastar achieves <strong>locality of behavior</strong> —
|
||
where markup, data, and interactivity remain close together.
|
||
This makes components easier to read, reason about, and maintain.
|
||
</p>
|
||
</v-click>
|
||
|
||
<v-click>
|
||
<p>
|
||
Datastar provides a set of <strong>built-in actions</strong> (helper functions) that can be used in Datastar expressions to interact with the backend.
|
||
The main actions are: <code>@get()</code>, <code>@post()</code>, <code>@put()</code>, <code>@patch()</code>, and <code>@delete()</code>.
|
||
</p>
|
||
|
||
```html
|
||
<button data-on:click="@get('/api/items')">Load Items</button>
|
||
|
||
```
|
||
</v-click>
|
||
|
||
|
||
|
||
---
|
||
layout: image
|
||
image: /mariner.png
|
||
transition: slide-up
|
||
level: 2
|
||
---
|
||
# to be continued ...
|
||
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
flowchart LR
|
||
%% Publishers
|
||
Scheduler["⏰ Scheduler<br/>(periodic tasks)"]
|
||
API["🌐 REST API<br/>(user actions)"]
|
||
Worker["⚙️ Background Job<br/>(processing tasks)"]
|
||
|
||
%% EventBus
|
||
EventBus["🧭 EventBus"]
|
||
|
||
%% Subscribers
|
||
SSE["📡 SSE Broadcaster<br/>(push to clients)"]
|
||
Logger["📝 Audit Logger<br/>(write to SQLite)"]
|
||
Cache["🗃 Cache Updater"]
|
||
Notifier["🔔 Notification Service"]
|
||
|
||
%% Client UI
|
||
Client["💻 Clients<br/> via SSE"]
|
||
|
||
%% Flows
|
||
Scheduler -->|publishes: task.tick| EventBus
|
||
API -->|publishes: ticket.created| EventBus
|
||
Worker -->|publishes: task.completed| EventBus
|
||
|
||
EventBus -->|subscribes: task.*| SSE
|
||
EventBus -->|subscribes: ticket.*| SSE
|
||
|
||
EventBus -->|subscribes: *| Logger
|
||
EventBus -->|subscribes: cache.invalidate| Cache
|
||
EventBus -->|subscribes: notify.*| Notifier
|
||
|
||
SSE -->|SSE stream| Client
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
flowchart LR
|
||
subgraph Publishers
|
||
Scheduler["⏰ Scheduler<br/>(periodic tasks)"]
|
||
API["🌐 API<br/>(user triggers)"]
|
||
Worker["⚙️ Background Job"]
|
||
end
|
||
|
||
EventBus["🧭 EventBus<br/>(pub/sub hub)"]
|
||
|
||
subgraph Subscribers
|
||
SSE["📡 SSE Broadcaster"]
|
||
Logger["📝 Audit Logger"]
|
||
Notifier["🔔 Notification Service"]
|
||
Cache["🗃 Cache Updater"]
|
||
end
|
||
|
||
Client["💻 Clients via SSE"]
|
||
|
||
Scheduler --> EventBus
|
||
API --> EventBus
|
||
Worker --> EventBus
|
||
|
||
EventBus --> SSE
|
||
EventBus --> Logger
|
||
EventBus --> Notifier
|
||
EventBus --> Cache
|
||
|
||
SSE --> Client
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
sequenceDiagram
|
||
participant P as Publisher (Scheduler)
|
||
participant EB as EventBus
|
||
participant S1 as SSE Broadcaster
|
||
participant S2 as Audit Logger
|
||
participant UI as Client UI
|
||
|
||
P->>EB: Publish("task.tick", data)
|
||
EB->>S1: Dispatch event
|
||
EB->>S2: Dispatch event
|
||
|
||
S1->>UI: Push via SSE
|
||
S2->>S2: Write to SQLite
|
||
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
classDiagram
|
||
class EventBus {
|
||
- mu sync.RWMutex
|
||
- subscribers map[string][]EventChannel
|
||
- stats *Stats
|
||
+ Publish(topic, data)
|
||
+ PublishAsync(topic, data)
|
||
+ Subscribe(topic) EventChannel
|
||
+ SubscribeCallback(topic, cb)
|
||
}
|
||
|
||
class EventChannel {
|
||
<<type>> chan Event
|
||
}
|
||
|
||
class Event {
|
||
Data map[string]interface
|
||
Topic string
|
||
wg *sync.WaitGroup
|
||
+ Done()
|
||
}
|
||
|
||
class Stats {
|
||
- data map[string]TopicStats
|
||
+ GetPublishedCountByTopic(t) int
|
||
+ GetSubscriberCountByTopic(t) int
|
||
}
|
||
|
||
EventBus --> EventChannel : delivers Events
|
||
EventBus --> Stats
|
||
Event --> Stats : increments counters
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
flowchart TD
|
||
Publish["Publish()"]
|
||
Mutex["RWMutex Lock"]
|
||
Channels["subscribers[topic]"]
|
||
GoRoutine["Goroutine for dispatch"]
|
||
Sub1["Subscriber #1"]
|
||
Sub2["Subscriber #2"]
|
||
|
||
Publish --> Mutex
|
||
Mutex --> Channels
|
||
Channels --> GoRoutine
|
||
GoRoutine --> Sub1
|
||
GoRoutine --> Sub2
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
flowchart TB
|
||
subgraph Backend
|
||
EB["EventBus"]
|
||
DB["SQLite Database"]
|
||
Scheduler
|
||
API
|
||
Worker
|
||
SSE["SSE Broadcaster"]
|
||
Logger
|
||
end
|
||
|
||
subgraph Clients
|
||
Browser["Browser UI"]
|
||
end
|
||
|
||
Scheduler --> EB
|
||
API --> EB
|
||
Worker --> EB
|
||
|
||
EB --> SSE
|
||
EB --> Logger
|
||
|
||
Logger --> DB
|
||
SSE --> Browser
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
flowchart LR
|
||
Client["🌐 Browser\n(HTML + JS)"]
|
||
|
||
subgraph Server["🖥 Go Application Server"]
|
||
EB["EventBus"]
|
||
SSE["SSE Handler"]
|
||
API["HTTP API"]
|
||
Worker["Background Worker"]
|
||
Scheduler["Cron Scheduler"]
|
||
DB["SQLite File"]
|
||
end
|
||
|
||
Client <--> SSE
|
||
Client <--> API
|
||
|
||
Scheduler --> Worker
|
||
Worker --> EB
|
||
API --> EB
|
||
EB --> SSE
|
||
EB --> DB
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
|
||
flowchart TD
|
||
A["1️⃣ Publisher creates event"]
|
||
B["2️⃣ EventBus receives event"]
|
||
C["3️⃣ Match subscribers (wildcard + exact)"]
|
||
D["4️⃣ Dispatch to channels"]
|
||
E["5️⃣ Subscribers handle event"]
|
||
F["6️⃣ Optional: push to client via SSE"]
|
||
G["7️⃣ Stats updated"]
|
||
|
||
A --> B --> C --> D --> E --> F --> G
|
||
|
||
```
|
||
---
|
||
class: default
|
||
---
|
||
|
||
# Eventbus Architecture
|
||
<div class="mt-10"/>
|
||
|
||
```mermaid { scale: 0.68}
|
||
flowchart LR
|
||
Topic["Incoming Topic: ticket.updated"]
|
||
Exact["Exact Match: ticket.updated"]
|
||
Wild1["Wildcard: ticket.*"]
|
||
Wild2["Wildcard: *"]
|
||
Result["Matched Subscribers"]
|
||
|
||
Topic --> Exact
|
||
Topic --> Wild1
|
||
Topic --> Wild2
|
||
|
||
Exact --> Result
|
||
Wild1 --> Result
|
||
Wild2 --> Result
|
||
|
||
|
||
```
|