🧮 USERS:1 FEEDS:2 TWTS:337 ARCHIVED:36480 CACHE:1537 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:336 ARCHIVED:36468 CACHE:1545 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:335 ARCHIVED:36404 CACHE:1542 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:334 ARCHIVED:36379 CACHE:1589 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:333 ARCHIVED:36357 CACHE:1579 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:332 ARCHIVED:36316 CACHE:1563 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:331 ARCHIVED:36304 CACHE:1600 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:330 ARCHIVED:36287 CACHE:1596 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1026, Archived: 1116347, Cache: 89202, Followers: 29, and Following: 701.
🧮 USERS:1 FEEDS:2 TWTS:329 ARCHIVED:36277 CACHE:1614 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:328 ARCHIVED:36263 CACHE:1622 FOLLOWERS:13 FOLLOWING:14
👋 Hello @burglar@txt.sour.is, welcome to txt.sour.is, a Yarn.social Pod! To get started you may want to check out the pod’s Discover feed to find users to follow and interact with. To follow new users, use the ⨁ Follow button on their profile page or use the Follow form and enter a Twtxt URL. You may also find other feeds of interest via Feeds. Welcome! 🤗
👋 Hello @burglar@txt.sour.is, welcome to txt.sour.is, a Yarn.social Pod! To get started you may want to check out the pod’s Discover feed to find users to follow and interact with. To follow new users, use the ⨁ Follow button on their profile page or use the Follow form and enter a Twtxt URL. You may also find other feeds of interest via Feeds. Welcome! 🤗
🧮 USERS:1 FEEDS:2 TWTS:327 ARCHIVED:36241 CACHE:1665 FOLLOWERS:13 FOLLOWING:14
I’ve realized that trying to strictly follow what is on the IndieWeb wiki won’t work well for me. Thus, I have to invent and change some things to make it work better. ⌘ Read more
(cont.)
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
Domain ObjectsA domain object can be used as an aggregate by adding the event.AggregateRoot struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string ```json:"identity"`
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
return "user-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
for _, e := range lis {
switch e := e.(type) {
case *UserCreated:
a.Identity = e.Identity
a.CreatedAt = e.EventMeta().CreatedDate
/* ... */
}
}
}
Events
Events are applied to the aggregate. They are defined by adding the event.Meta and implementing the getter/setters for event.Event
type UserCreated struct {
eventMeta event.Meta
Identity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
if c != nil {
m = c.eventMeta
}
return m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
if c != nil {
c.eventMeta = m
}
}
Reading Events from EventStore
With a domain object that implements the event.Aggregate the event store client can load events and apply them using the Load(ctx, agg) method.
// GetUser populates an user from event store.
func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) {
user := &domain.User{Identity: userID}
err := rw.es.Load(ctx, user)
if err != nil {
if err != nil {
if errors.Is(err, eventstore.ErrStreamNotFound) {
return user, ErrNotFound
}
return user, err
}
return nil, err
}
return user, err
}
OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the user.
// Note: The handler will check that the user does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
if a.State != TaskStateCreated {
return fmt.Errorf("task expected created, got %s", a.State)
}
event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
return nil
}
Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
(cont.)
Just to give some context on some of the components around the code structure.. I wrote this up around an earlier version of aggregate code. This generic bit simplifies things by removing the need of the Crud functions for each aggregate.
Domain ObjectsA domain object can be used as an aggregate by adding the event.AggregateRoot struct and finish implementing event.Aggregate. The AggregateRoot implements logic for adding events after they are either Raised by a command or Appended by the eventstore Load or service ApplyFn methods. It also tracks the uncommitted events that are saved using the eventstore Save method.
type User struct {
Identity string ```json:"identity"`
CreatedAt time.Time
event.AggregateRoot
}
// StreamID for the aggregate when stored or loaded from ES.
func (a *User) StreamID() string {
return "user-" + a.Identity
}
// ApplyEvent to the aggregate state.
func (a *User) ApplyEvent(lis ...event.Event) {
for _, e := range lis {
switch e := e.(type) {
case *UserCreated:
a.Identity = e.Identity
a.CreatedAt = e.EventMeta().CreatedDate
/* ... */
}
}
}
Events
Events are applied to the aggregate. They are defined by adding the event.Meta and implementing the getter/setters for event.Event
type UserCreated struct {
eventMeta event.Meta
Identity string
}
func (c *UserCreated) EventMeta() (m event.Meta) {
if c != nil {
m = c.eventMeta
}
return m
}
func (c *UserCreated) SetEventMeta(m event.Meta) {
if c != nil {
c.eventMeta = m
}
}
Reading Events from EventStore
With a domain object that implements the event.Aggregate the event store client can load events and apply them using the Load(ctx, agg) method.
// GetUser populates an user from event store.
func (rw *User) GetUser(ctx context.Context, userID string) (*domain.User, error) {
user := &domain.User{Identity: userID}
err := rw.es.Load(ctx, user)
if err != nil {
if err != nil {
if errors.Is(err, eventstore.ErrStreamNotFound) {
return user, ErrNotFound
}
return user, err
}
return nil, err
}
return user, err
}
OnX Commands
An OnX command will validate the state of the domain object can have the command performed on it. If it can be applied it raises the event using event.Raise() Otherwise it returns an error.
// OnCreate raises an UserCreated event to create the user.
// Note: The handler will check that the user does not already exsist.
func (a *User) OnCreate(identity string) error {
event.Raise(a, &UserCreated{Identity: identity})
return nil
}
// OnScored will attempt to score a task.
// If the task is not in a Created state it will fail.
func (a *Task) OnScored(taskID string, score int64, attributes Attributes) error {
if a.State != TaskStateCreated {
return fmt.Errorf("task expected created, got %s", a.State)
}
event.Raise(a, &TaskScored{TaskID: taskID, Attributes: attributes, Score: score})
return nil
}
Crud Operations for OnX Commands
The following functions in the aggregate service can be used to perform creation and updating of aggregates. The Update function will ensure the aggregate exists, where the Create is intended for non-existent aggregates. These can probably be combined into one function.
// Create is used when the stream does not yet exist.
func (rw *User) Create(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil && !errors.Is(err, ErrNotFound) {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
// Update is used when the stream already exists.
func (rw *User) Update(
ctx context.Context,
identity string,
fn func(*domain.User) error,
) (*domain.User, error) {
session, err := rw.GetUser(ctx, identity)
if err != nil {
return nil, err
}
if err = fn(session); err != nil {
return nil, err
}
_, err = rw.es.Save(ctx, session)
return session, err
}
🧮 USERS:1 FEEDS:2 TWTS:326 ARCHIVED:36217 CACHE:1658 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:325 ARCHIVED:36174 CACHE:1662 FOLLOWERS:13 FOLLOWING:14
The XMPP Standards Foundation: Mid Term Evaluation Updates
It’s been a month since I wrote my last blog. For those of you who have been following my blogs, thanks a lot for taking the time to read them. In this blog, I will give the updates post mid-term evaluation and the challenges that I have been facing and how I overcame some of them.
For those of you who don’t know much about GSoC, a mid-term evaluat … ⌘ Read more
🧮 USERS:1 FEEDS:2 TWTS:324 ARCHIVED:36126 CACHE:1630 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:323 ARCHIVED:36095 CACHE:1620 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:322 ARCHIVED:36071 CACHE:1610 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:321 ARCHIVED:36041 CACHE:1614 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:320 ARCHIVED:36015 CACHE:1629 FOLLOWERS:13 FOLLOWING:14
To all my feed subscribers: If you are annoyed by the TTS audio or the “Interactions” link, add a “.min” in front of the feed type in the URL. For example https://jlelse.blog/.min.rss. Thanks for following! 😄 ⌘ Read more
🧮 USERS:1 FEEDS:2 TWTS:319 ARCHIVED:35977 CACHE:1621 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:318 ARCHIVED:35951 CACHE:1608 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:317 ARCHIVED:35790 CACHE:1611 FOLLOWERS:13 FOLLOWING:14
Ignite Realtime Blog: REST API Openfire plugin 1.9.1 released!
Woopsie doodle! It turns out that a rather annoying bug was introduced in version 1.9.0 of the REST API plugin for Openfire, that we released earlier today!
To avoid unnecessary issues, we’ve decided to follow up with an immediate new release that addresses this issue. … ⌘ Read more
🧮 USERS:1 FEEDS:2 TWTS:316 ARCHIVED:35745 CACHE:1618 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:315 ARCHIVED:35714 CACHE:1620 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:314 ARCHIVED:35694 CACHE:1628 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:313 ARCHIVED:35653 CACHE:1609 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:312 ARCHIVED:35548 CACHE:1535 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:311 ARCHIVED:35521 CACHE:1525 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:310 ARCHIVED:35401 CACHE:1496 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:309 ARCHIVED:35369 CACHE:1481 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:308 ARCHIVED:35338 CACHE:1485 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:307 ARCHIVED:35290 CACHE:1458 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:306 ARCHIVED:35250 CACHE:1455 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:305 ARCHIVED:35225 CACHE:1459 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:304 ARCHIVED:35175 CACHE:1452 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:303 ARCHIVED:35163 CACHE:1475 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:302 ARCHIVED:35148 CACHE:1473 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:301 ARCHIVED:35133 CACHE:1463 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:300 ARCHIVED:35117 CACHE:1470 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:299 ARCHIVED:35064 CACHE:1473 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:298 ARCHIVED:35034 CACHE:1450 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:297 ARCHIVED:35006 CACHE:1434 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:296 ARCHIVED:34976 CACHE:1413 FOLLOWERS:13 FOLLOWING:14
Minkowski Space
⌘ Read more
🧮 USERS:1 FEEDS:2 TWTS:295 ARCHIVED:34938 CACHE:1394 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:294 ARCHIVED:34917 CACHE:1389 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:293 ARCHIVED:34885 CACHE:1387 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:292 ARCHIVED:34863 CACHE:1384 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:291 ARCHIVED:34842 CACHE:1418 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:290 ARCHIVED:34827 CACHE:1421 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:289 ARCHIVED:34810 CACHE:1428 FOLLOWERS:13 FOLLOWING:14
No more Telegram Premium
Telegram decided to cancel my Premium, I got the following message from the @PremiumBot: ⌘ Read more
🧮 USERS:1 FEEDS:2 TWTS:288 ARCHIVED:34804 CACHE:1427 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:287 ARCHIVED:34793 CACHE:1449 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:286 ARCHIVED:34787 CACHE:1461 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:285 ARCHIVED:34767 CACHE:1465 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:284 ARCHIVED:34741 CACHE:1467 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:283 ARCHIVED:34725 CACHE:1489 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:282 ARCHIVED:34707 CACHE:1492 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:281 ARCHIVED:34685 CACHE:1484 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:280 ARCHIVED:34669 CACHE:1493 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:279 ARCHIVED:34650 CACHE:1498 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:278 ARCHIVED:34640 CACHE:1502 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:277 ARCHIVED:34627 CACHE:1503 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:276 ARCHIVED:34470 CACHE:1475 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:275 ARCHIVED:34441 CACHE:1479 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:274 ARCHIVED:34430 CACHE:1492 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:273 ARCHIVED:34398 CACHE:1491 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:272 ARCHIVED:34354 CACHE:1524 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:271 ARCHIVED:34334 CACHE:1534 FOLLOWERS:13 FOLLOWING:14
following legislation from the ELSC (european long site commission), I now have to adopt an ocelot
🧮 USERS:1 FEEDS:2 TWTS:270 ARCHIVED:34313 CACHE:1557 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:269 ARCHIVED:34277 CACHE:1541 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:268 ARCHIVED:34262 CACHE:1550 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:267 ARCHIVED:34252 CACHE:1547 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:266 ARCHIVED:34234 CACHE:1551 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:265 ARCHIVED:34209 CACHE:1542 FOLLOWERS:13 FOLLOWING:14
🧮 USERS:1 FEEDS:2 TWTS:264 ARCHIVED:34172 CACHE:1525 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1007, Archived: 886396, Cache: 121354, Followers: 29, and Following: 699.
🧮 USERS:1 FEEDS:2 TWTS:263 ARCHIVED:34140 CACHE:1500 FOLLOWERS:13 FOLLOWING:14
My current state regarding meal replacements
In my 2019 Year in Review, I wrote the following: ⌘ Read more
🧮 Users: 2, Feeds: 6, Twts: 1006, Archived: 883595, Cache: 122433, Followers: 29, and Following: 699.
🧮 USERS:1 FEEDS:2 TWTS:262 ARCHIVED:34084 CACHE:1458 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1005, Archived: 880048, Cache: 122224, Followers: 29, and Following: 699.
🧮 USERS:1 FEEDS:2 TWTS:261 ARCHIVED:34050 CACHE:1450 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1004, Archived: 876133, Cache: 122028, Followers: 29, and Following: 699.
🧮 USERS:1 FEEDS:2 TWTS:260 ARCHIVED:33574 CACHE:1553 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1003, Archived: 872231, Cache: 119530, Followers: 29, and Following: 699.
🧮 USERS:1 FEEDS:2 TWTS:259 ARCHIVED:33535 CACHE:1536 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1002, Archived: 868478, Cache: 123331, Followers: 29, and Following: 699.
🧮 USERS:1 FEEDS:2 TWTS:258 ARCHIVED:33518 CACHE:1529 FOLLOWERS:13 FOLLOWING:14
🧮 Users: 2, Feeds: 6, Twts: 1001, Archived: 865325, Cache: 121064, Followers: 29, and Following: 699.