artem

artem

CQRS/Commanded and counting credits/usage

Hi all

I am learning CQRS and Commanded by doing a project (so main point is learning), I’ve got to the point where it is already doing something useful on my laptop: you type a news article and then it uses ChatGPT for crafting some clever posts about it + you can tune the response with supposedly cool UI.

Now I want to make it public and let people play with it for free, but within some limits so I don’t get bankrupt on a hobby project (say, some 20-50 generations for user per month or per whole user lifetime) and… how do you do it in CQRS? :slight_smile:

Non-CQRS ways
I come from Java/SpringBoot world so a relatively natural way for me would be to have some sort of credits field in the customer or user object. Then before doing something expensive (ChatGPT generation in this case) I’d check if there are credits available and decrease them in the same transaction. If generation fails, then some fail handler may or may not reuse credit back.

Ways for CQRS and specifically commanded
As everything in CQRS is eventually consistent, does it mean that I need to “book” some credits, make it somehow possible to monitor when the “booking” is completed (via some projection with a booking request id?) and only then do a request?

Or is possible to rely on Commanded’s strong consistency (or maybe it’s enough to specify option returning: aggregate_state when dispatching) and once dispatch has happened I can already know if the credits were enough or not.

Or am I missing some other obvious options? Like is it possibly common for exactly “credit checks” kind of use cases live outside CQRS so that DB transactions could be used for consistency protections and access serialization?

Again, my main point here is learning (though I still want to open project to public), so I’ll highly appreciate any hints and ideas, especially if ever did anything like that in CQRS.

P.S.
Even though it’s mostly self-education project almost invented just so I could have a real use case to play with CQRS, there is still some reasoning for exactly CQRS. My main technical motivation is about separating read-write models so I could have as many and as convenient read models as I like. Access serialization and consistency are way less important for me in this particular case.

First Post!

byu

byu

Caveat: I’m a major beginner in EventSourcing and CQRS.

What about something like the following?

  1. Manage an Event Stream (aggregate) per User per Refresh Period (month, year, all time).
    • eg generation-account-bob-202402
    • This tracks not just the count, but the lifecycle of each generation request.
      • pending, completed, failed to complete, rejected.
    • Also can have event that indicates the number of allowed credits.
  2. Use a SubmitGenerationCommand that includes
    • request identifier
    • request payload/details about the generation TBD
  3. Handling the SubmitGenerationCommand can emit Events
    • GenerationSubmittedEvent – with request identifier
    • GenerationRequestRejectedEvent – with details such as:
      • max pending requests limit reached
      • max credits reached.
  4. An Event Handler or Process Manager can watch for GenerationSubmittedEvent and then issue commands or execute API requests?
    • Then somehow events get back into the “generation-accounts” stream that would indicate success or failure?
  5. And more Event Handlers would watch for Success/Failure events to then send message to update the “conversation”?

Basically, requests go into a “rate limiter” aggregate for that user-period. The Events of Submitted/Completed/Failed/Rejected requests allow the single aggregate to track the used credits count, pending credits such that used credits + pending <= allowed credits ? and can reject requests over the limit?

And follow-on Event Handlers will eventual consistently process (succeed or fail) the submitted requests?

The above uses patterns:

  • Closing the Books – like accounting, so we can reset the user’s credit each period
  • Many small streams, instead of big streams – A small stream of data for each user-time-period instead of a single stream for the user of all time.
  • Write Ahead Pattern – we are writing the “request submitted” event to hold the pending place, then a Completion/Failed event to confirm the credit usage

We avoid:

  • Reservation Pattern (book keeping) – because if the credit is reserved/booked, that reservation has an expiration because that ticket may never have been used/completed… And even then, the generation processor that receives the ticket and executes for a GenerationCompleted response MAY race condition with the ExpirationEvent.

[edit]: Here’s a suggested video that also talks about how to do different kinds of “business transactions” that spans multiple streams:

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement