Skip to content

Commit e44cf37

Browse files
committed
doc: add recommended patterns for custom quorum functions
This adds several examples of recommended patterns for custom quorum functions, including with custom return types.
1 parent f5c011e commit e44cf37

File tree

1 file changed

+108
-25
lines changed

1 file changed

+108
-25
lines changed

doc/user-guide.md

Lines changed: 108 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -528,20 +528,18 @@ func fastQuorum(responses *gorums.Responses[*Response]) (*Response, error) {
528528
## Custom Return Types
529529
530530
Gorums supports custom aggregation functions that return types different from the proto response type.
531-
This is useful when you need to aggregate multiple responses into a summary or statistics object.
531+
This is useful when you need to aggregate multiple responses into a summary, statistics object, or any other custom type.
532532
533-
### Example: Collecting Multiple Responses
533+
### Recommended Pattern: Functions Taking `*Responses[Resp]`
534534
535-
Consider a `StopBenchmark` RPC that returns `*MemoryStat` from each node, but you want to return `*MemoryStatList` containing all stats:
535+
The recommended approach is to define functions that accept `*Responses[Resp]` directly.
536+
This gives you full access to all iterator methods (`IgnoreErrors()`, `Filter()`, `CollectN()`, `CollectAll()`) and the ability to return any type.
536537
537538
```go
538-
// Proto definitions:
539-
// rpc StopBenchmark(StopRequest) returns (MemoryStat) { option (gorums.quorumcall) = true; }
540-
// message MemoryStat { uint64 allocs = 1; uint64 memory = 2; }
541-
// message MemoryStatList { repeated MemoryStat memory_stats = 1; }
542-
543-
// Custom aggregation function with different return type
544-
func StopBenchmarkQF(replies map[uint32]*MemoryStat) (*MemoryStatList, error) {
539+
// Custom aggregation function that returns a different type
540+
// Input: *Responses[*MemoryStat], Output: *MemoryStatList
541+
func CollectStats(resp *gorums.Responses[*MemoryStat]) (*MemoryStatList, error) {
542+
replies := resp.IgnoreErrors().CollectAll()
545543
if len(replies) == 0 {
546544
return nil, gorums.ErrIncomplete
547545
}
@@ -550,36 +548,121 @@ func StopBenchmarkQF(replies map[uint32]*MemoryStat) (*MemoryStatList, error) {
550548
}, nil
551549
}
552550

553-
// Usage: Two-step pattern
551+
// Usage: Call the function directly, passing the Responses object
554552
cfgCtx := config.Context(ctx)
555-
responses := StopBenchmark(cfgCtx, &StopRequest{})
556-
replies := responses.Seq().IgnoreErrors().CollectAll() // map[uint32]*MemoryStat
557-
memStats, err := StopBenchmarkQF(replies) // Returns *MemoryStatList
553+
memStats, err := CollectStats(StopBenchmark(cfgCtx, &StopRequest{}))
554+
```
555+
556+
### Example: Same Type Aggregation
557+
558+
When the return type matches the response type, you can still use this pattern for custom quorum logic:
559+
560+
```go
561+
// Custom majority quorum with validation
562+
func ValidatedMajority(resp *gorums.Responses[*State]) (*State, error) {
563+
replies := resp.IgnoreErrors().CollectN(resp.Size()/2 + 1)
564+
if len(replies) < resp.Size()/2+1 {
565+
return nil, gorums.ErrIncomplete
566+
}
567+
// Return the first valid reply
568+
for _, r := range replies {
569+
if isValid(r) {
570+
return r, nil
571+
}
572+
}
573+
return nil, gorums.ErrIncomplete
574+
}
575+
576+
// Usage
577+
cfgCtx := config.Context(ctx)
578+
state, err := ValidatedMajority(ReadQC(cfgCtx, &ReadRequest{}))
579+
```
580+
581+
### Example: Custom Return Type (Slice)
582+
583+
```go
584+
// Collect all string values from responses
585+
func CollectAllValues(resp *gorums.Responses[*StringValue]) ([]string, error) {
586+
replies := resp.IgnoreErrors().CollectAll()
587+
if len(replies) == 0 {
588+
return nil, gorums.ErrIncomplete
589+
}
590+
result := make([]string, 0, len(replies))
591+
for _, v := range replies {
592+
result = append(result, v.GetValue())
593+
}
594+
return result, nil
595+
}
596+
597+
// Usage: Returns []string instead of *StringValue
598+
values, err := CollectAllValues(GetValues(cfgCtx, &Request{}))
558599
```
559600
560601
### Example: Computing Aggregate Statistics
561602
562603
```go
563-
// Aggregate results from multiple nodes
564-
func AggregateResults(replies map[uint32]*Result) (*Result, error) {
604+
// Aggregate results from multiple nodes into a summary
605+
func AggregateResults(resp *gorums.Responses[*Result]) (*Result, error) {
606+
replies := resp.IgnoreErrors().CollectAll()
565607
if len(replies) == 0 {
566608
return nil, gorums.ErrIncomplete
567609
}
568610

569-
resp := &Result{}
611+
summary := &Result{}
570612
for _, reply := range replies {
571-
resp.TotalOps += reply.TotalOps
572-
resp.TotalTime += reply.TotalTime
573-
resp.Throughput += reply.Throughput
613+
summary.TotalOps += reply.TotalOps
614+
summary.TotalTime += reply.TotalTime
615+
summary.Throughput += reply.Throughput
574616
}
575617

576618
// Calculate averages
577-
numNodes := len(replies)
578-
resp.TotalOps /= uint64(numNodes)
579-
resp.TotalTime /= int64(numNodes)
580-
resp.Throughput /= float64(numNodes)
619+
n := uint64(len(replies))
620+
summary.TotalOps /= n
621+
summary.TotalTime /= int64(n)
622+
summary.Throughput /= float64(n)
623+
624+
return summary, nil
625+
}
626+
```
627+
628+
### Example: Returning a Primitive Type
629+
630+
```go
631+
// Count responses from specific nodes
632+
func CountFromPrimaryNodes(resp *gorums.Responses[*Response]) (int, error) {
633+
count := 0
634+
for r := range resp.IgnoreErrors().Filter(func(nr gorums.NodeResponse[*Response]) bool {
635+
return isPrimaryNode(nr.NodeID)
636+
}) {
637+
count++
638+
}
639+
if count == 0 {
640+
return 0, gorums.ErrIncomplete
641+
}
642+
return count, nil
643+
}
644+
```
581645
582-
return resp, nil
646+
### Example: Explicit Error Handling
647+
648+
When you need to handle errors from individual nodes explicitly:
649+
650+
```go
651+
// Require all nodes to succeed
652+
func RequireAllSuccess(resp *gorums.Responses[*Response]) (*Response, error) {
653+
var first *Response
654+
for r := range resp.Seq() {
655+
if r.Err != nil {
656+
return nil, r.Err // Fail fast on any error
657+
}
658+
if first == nil {
659+
first = r.Value
660+
}
661+
}
662+
if first == nil {
663+
return nil, gorums.ErrIncomplete
664+
}
665+
return first, nil
583666
}
584667
```
585668

0 commit comments

Comments
 (0)