优化结构

This commit is contained in:
Yvan
2026-08-21 23:29:28 +08:00
parent 5d5784f535
commit 626f505b5d
3 changed files with 180 additions and 38 deletions
+37
View File
@@ -10,6 +10,11 @@ import (
var ErrUnavailable = errors.New("message broker unavailable")
const (
ProviderEMQX = "emqx"
ProviderRabbitMQ = "rabbitmq"
)
const (
AtMostOnce byte = 0
AtLeastOnce byte = 1
@@ -27,6 +32,37 @@ func (m Message) DecodeJSON(target any) error { return json.Unmarshal(m.Payload,
type Handler func(context.Context, Message)
// TopicSubscription is one logical module subscription. The handler is kept
// by the runtime and is replayed after a client reconnects or is rebuilt.
type TopicSubscription struct {
Topic string
QoS byte
Handler Handler
}
// SubscriptionSet is the complete subscription declaration for one module and
// provider. Registering the same owner/provider replaces its previous set.
// Owner must be a stable module name, not a request or goroutine identifier.
type SubscriptionSet struct {
Owner string
Provider string
Topics []TopicSubscription
}
// SubscriptionRegistrar is the module-facing seam for durable-in-process
// subscription intent. Register does not require a live broker; the runtime
// will bind the declared topics when the provider becomes available.
type SubscriptionRegistrar interface {
Register(SubscriptionSet) error
Unregister(owner string) error
}
// SubscriptionContributor lets a module expose its broker subscriptions
// without depending on the concrete integration implementation.
type SubscriptionContributor interface {
RegisterSubscriptions(SubscriptionRegistrar) error
}
type Client interface {
Publish(context.Context, string, []byte, byte, bool) error
Subscribe(context.Context, string, byte, Handler) error
@@ -38,6 +74,7 @@ type Client interface {
// Registry exposes named broker clients while preserving Client as the
// default EMQX/MQTT boundary for existing modules.
type Registry interface {
SubscriptionRegistrar
Client(provider string) Client
PublishTo(context.Context, string, string, []byte, byte, bool) error
SubscribeTo(context.Context, string, string, byte, Handler) error
+55
View File
@@ -0,0 +1,55 @@
package mq
import (
"fmt"
"strings"
)
// NormalizeSubscriptionSet validates and canonicalizes a module declaration.
func NormalizeSubscriptionSet(set SubscriptionSet) (SubscriptionSet, error) {
set.Owner = strings.TrimSpace(set.Owner)
set.Provider = strings.ToLower(strings.TrimSpace(set.Provider))
if set.Owner == "" {
return SubscriptionSet{}, fmt.Errorf("mq subscription owner is empty")
}
if set.Provider != ProviderEMQX && set.Provider != ProviderRabbitMQ {
return SubscriptionSet{}, fmt.Errorf("unsupported mq provider %q", set.Provider)
}
if len(set.Topics) == 0 {
return SubscriptionSet{}, fmt.Errorf("mq subscription topics are empty")
}
seen := make(map[string]struct{}, len(set.Topics))
for index := range set.Topics {
set.Topics[index].Topic = strings.TrimSpace(set.Topics[index].Topic)
if set.Topics[index].Topic == "" {
return SubscriptionSet{}, fmt.Errorf("mq subscription topic at index %d is empty", index)
}
if set.Topics[index].QoS > ExactlyOnce {
return SubscriptionSet{}, fmt.Errorf("invalid mq qos %d for topic %q", set.Topics[index].QoS, set.Topics[index].Topic)
}
if set.Topics[index].Handler == nil {
return SubscriptionSet{}, fmt.Errorf("mq subscription handler is nil for topic %q", set.Topics[index].Topic)
}
if _, exists := seen[set.Topics[index].Topic]; exists {
return SubscriptionSet{}, fmt.Errorf("duplicate mq subscription topic %q", set.Topics[index].Topic)
}
seen[set.Topics[index].Topic] = struct{}{}
}
return set, nil
}
// ApplySubscriptions activates dependency-bearing module contributors.
func ApplySubscriptions(registrar SubscriptionRegistrar, contributors ...SubscriptionContributor) error {
if registrar == nil {
return fmt.Errorf("mq subscription registrar is nil")
}
for _, contributor := range contributors {
if contributor == nil {
continue
}
if err := contributor.RegisterSubscriptions(registrar); err != nil {
return err
}
}
return nil
}