case studies

A Q4 Price List Goes Out as One Transaction With One Reason

Prices change together. A new quarter’s price list touches every plan at once, and a half-applied list is worse than an old one: for a few minutes Pro might cost less than Basic. The list also has one reason behind it, usually a decision made in a meeting, with a ticket that tracked the rollout.

XKV writes the whole list in one transaction. Every price commits or none does, and every price carries the same reason, ticket and cause, given once.

A week later a customer complains that Pro now costs more than two Basic seats, and Pro is corrected. Its history now holds two changes with two different reasons, and both are easy to read back:

The why tree for price:pro: 29, set by pricing-team to keep Pro under two Basic seats, caused by support ticket SUP-5521 and by Pro’s previous price of 33, which went out in the Q4 price list under ticket PRICE-12, caused by the Q4 pricing decision recorded under FIN-208.

The Price List

The decision goes in first, as a value of its own, so prices can point at it:

decision, err := db.Put(ctx, "decision:q4-pricing",
	[]byte("raise list prices 10%, hold Basic"),
	xkv.Reason("Q4 planning meeting"), xkv.Ref("FIN-208"))

Then every price in one Update. The options given to Update apply to each write inside it:

err = db.Update(ctx, func(tx *xkv.Tx) error {
	for _, p := range [][2]string{
		{"price:basic", "9"}, {"price:pro", "33"}, {"price:team", "66"},
	} {
		if _, err := tx.Put(p[0], []byte(p[1])); err != nil {
			return err
		}
	}
	return nil
}, xkv.Reason("Q4 price list"), xkv.Ref("PRICE-12"), xkv.Cause(decision.Key, decision.Rev))

If any write fails, the function returns an error and nothing is written. Readers never see a mix of old and new prices, because a transaction becomes visible all at once.

The Correction

Support records the complaint, and pricing corrects Pro. The correction names two causes: the complaint, and Pro’s own previous price, since the new price is a change to that one.

_, err = db.Put(ctx, "price:pro", []byte("29"),
	xkv.Reason("keep Pro under two Basic seats"),
	xkv.Cause(complaint.Key, complaint.Rev),
	xkv.CauseLatest("price:pro"))

Reading It Back

price:pro = 29   r6
│ was 33 · by pricing-team · keep Pro under two Basic seats
├─ ticket:SUP-5521 = Pro now costs more than two Basic seats   r5
│    by support · customer complaint
└─ price:pro = 33   r3
   │ by pricing-team · Q4 price list · ref PRICE-12
   └─ decision:q4-pricing = raise list prices 10%, hold Basic   r1
        by pricing-team · Q4 planning meeting · ref FIN-208

The tree runs from today’s price back to the meeting that started it, through the complaint that changed it. History gives the flat version, newest first, with the transaction each price went out in. Finance can follow the decision; support can find the ticket; and the next person to change Pro sees both before they start.

Run It Yourself

git clone https://github.com/explainable-kv/xkv
cd xkv
go run ./examples/price-list