Actyx / Actyx

Local-First Cooperation based on Event Sourcing

Home Page:https://developer.actyx.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`ActyxClient` subscribe methods are too verbose

jmg-duarte opened this issue · comments

There's a bunch of reasons the API is verbose:

  • Requires Strings instead of &str (taking ownership inside the function)
  • Methods require structures with the same name as the method, leading to the repetition of names through out

Example:

client
    .subscribe_monotonic(SubscribeMonotonicRequest {
        session: SessionId::from("me"),
        query: "FROM appId(com.example.chat) & 'message'".to_owned(),
        from: StartFrom::LowerBound(OffsetMap::empty()),
    })
    .await
    .expect("failed to subscribe to messages")

From here, the only truly required field is query.

One option is changing the subscribe_monotonic to receive just query instead:

client
    .subscribe_monotonic("FROM appId(com.example.chat) & 'message'")
    .await
    .expect("failed to subscribe to messages")

This would initialize the request inside the method instead of requiring the user to do so.

Another option would be to use Default and allow the user to write:

client
    .subscribe_monotonic(SubscribeMonotonicRequest {
        query: "FROM appId(com.example.chat) & 'message'".to_owned(),
        Default::default()..
    })
    .await
    .expect("failed to subscribe to messages")

Yet another (more generic) option would be to create a public request method, that receives these Request objects:

enum Request { SubscribeMonotonic(SubscribeMonotonicRequest), ... }
fn request(request: Request) {
    match request {
        SubscribeMonotonic(request) => { ... }
    }
}

We could even make the SubscribeMonotonic inside the enumeration a structure instead of an anonymous tuple (apologies if the name is wrong). Serialization could be made untagged so the underlying representation stays the same too.

The main argument to subscribe (both variants) is the query string, which we should take as impl Into<String> (people know what that means). The ability to specify a lower bound (or other options) should be discoverable, which means we cannot use “method overloading by trait” (e.g. taking an impl Into<SubscribeMonotonicRequest>). This implies that a second method like subscribe_monotonic_opts might be a good way, what do you think?

This implies that a second method like subscribe_monotonic_opts might be a good way, what do you think?

I like the Opts approach but instead of a second method, maybe we could once more either provide a Default or make it Option<SubscribeOpts>.

In the end, I see the "simple" version (i.e. fn publish<S: Into<String>>(&self, query: S)) calling the opts version with some sort of defaults — for example, the session ID: SessionId::from("me") or some similar mechanism as we can't run away from providing those other arguments.

Right, some method needs to supply all the arguments. The main difference is

ax.subscribe("FROM 'message'");
ax.subscribe_opts("FROM 'message'", SubscribeOpts { lower_bound, Default::default().. });

// vs

ax.subscribe("FROM 'message'", None);
ax.subscribe("FROM 'message'", Some(SubscribeOpts { lower_bound, Default::default().. }));