Skip to content

Commit 5035c26

Browse files
author
Marko Petzold
committed
router: expose BrokerFactory / DealerFactory hooks on RealmConfig
Final step of the Broker/Dealer interface extraction. Adds two new factory-function fields on RealmConfig: type BrokerFactory func(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Broker, error) type DealerFactory func(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Dealer, error) When unset (default), the router builds the in-process actor-loop broker/dealer via defaultBrokerFactory / defaultDealerFactory — same behavior as before. When set, downstream consumers can supply their own implementations: a clustered Raft-FSM-backed broker, a metrics-wrapping decorator, a forwarding proxy that routes calls across nodes, etc. The factory receives the full RealmConfig + logger so custom impls have everything the defaults need. Pinned by: - TestRealmConfigBrokerAndDealerFactoriesHonored — wraps the default impls in probe types and asserts the hooks are actually called. - TestRealmConfigUnsetFactoriesUseDefaults — checks the fallback path keeps working (concrete *broker / *dealer dynamic types). This is the seam Phase 2 clustering depends on. The 36-plan spec/ harness, race-checked router suite, and cross-transport test/ matrix all pass unchanged. Coverage 63.2%. Step 3 of 3 in gammazero#315 / IRONFLOCK_PLAN §1.2.
1 parent 3a4e193 commit 5035c26

4 files changed

Lines changed: 145 additions & 7 deletions

File tree

router/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ type RealmConfig struct {
9090
// This value is not set via json config, but is configured when embedding
9191
// nexus. A value of nil enables the default filtering.
9292
PublishFilterFactory FilterFactory
93+
94+
// BrokerFactory, if set, supplies a custom Broker implementation for
95+
// this realm instead of the default in-process broker. Useful for
96+
// clustered backends, metrics-wrapping decorators, or forwarding
97+
// proxies. nil means use the default.
98+
BrokerFactory BrokerFactory
99+
100+
// DealerFactory, if set, supplies a custom Dealer implementation for
101+
// this realm instead of the default in-process dealer.
102+
DealerFactory DealerFactory
93103
}
94104

95105
type TopicEventHistoryConfig struct {

router/factory_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package router //nolint:testpackage
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/gammazero/nexus/v3/stdlog"
9+
"github.com/gammazero/nexus/v3/wamp"
10+
)
11+
12+
// brokerFactoryProbe wraps the default broker but records that the
13+
// factory was invoked, so the test can assert RealmConfig.BrokerFactory
14+
// is honored when set.
15+
type brokerFactoryProbe struct {
16+
Broker
17+
called bool
18+
}
19+
20+
// dealerFactoryProbe is the analogous wrapper for Dealer.
21+
type dealerFactoryProbe struct {
22+
Dealer
23+
called bool
24+
}
25+
26+
// TestRealmConfigBrokerAndDealerFactoriesHonored verifies that custom
27+
// BrokerFactory / DealerFactory hooks on RealmConfig are invoked
28+
// instead of the default in-process implementations. Critical for
29+
// downstream consumers (e.g. clustered nexus, ironflock-router) that
30+
// need to substitute their own broker/dealer.
31+
func TestRealmConfigBrokerAndDealerFactoriesHonored(t *testing.T) {
32+
const realmURI = wamp.URI("nexus.test.factory")
33+
34+
var brokerProbe *brokerFactoryProbe
35+
var dealerProbe *dealerFactoryProbe
36+
37+
r, err := NewRouter(&Config{
38+
RealmConfigs: []*RealmConfig{
39+
{
40+
URI: realmURI,
41+
StrictURI: false,
42+
BrokerFactory: func(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Broker, error) {
43+
inner, err := defaultBrokerFactory(cfg, logger, debug)
44+
if err != nil {
45+
return nil, err
46+
}
47+
brokerProbe = &brokerFactoryProbe{Broker: inner, called: true}
48+
return brokerProbe, nil
49+
},
50+
DealerFactory: func(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Dealer, error) {
51+
inner, err := defaultDealerFactory(cfg, logger, debug)
52+
if err != nil {
53+
return nil, err
54+
}
55+
dealerProbe = &dealerFactoryProbe{Dealer: inner, called: true}
56+
return dealerProbe, nil
57+
},
58+
},
59+
},
60+
}, logger)
61+
require.NoError(t, err)
62+
t.Cleanup(func() { r.Close() })
63+
64+
require.NotNil(t, brokerProbe, "BrokerFactory was not called")
65+
require.True(t, brokerProbe.called)
66+
require.NotNil(t, dealerProbe, "DealerFactory was not called")
67+
require.True(t, dealerProbe.called)
68+
}
69+
70+
// TestRealmConfigUnsetFactoriesUseDefaults exercises the fallback path
71+
// — when neither factory is set, the realm uses the default in-process
72+
// broker/dealer. Indirectly verified by every other test in this
73+
// package, but pinned here so a future refactor that breaks the
74+
// default selection fails loud at this single test.
75+
func TestRealmConfigUnsetFactoriesUseDefaults(t *testing.T) {
76+
const realmURI = wamp.URI("nexus.test.factory.default")
77+
78+
r, err := NewRouter(&Config{
79+
RealmConfigs: []*RealmConfig{
80+
{URI: realmURI, StrictURI: false, AnonymousAuth: true},
81+
},
82+
}, logger)
83+
require.NoError(t, err)
84+
t.Cleanup(func() { r.Close() })
85+
86+
// Reach into the realm to inspect the dynamic types of broker / dealer.
87+
rl := r.(*router).realms[realmURI]
88+
require.NotNil(t, rl)
89+
_, isDefaultBroker := rl.broker.(*broker)
90+
require.True(t, isDefaultBroker, "expected default *broker; got %T", rl.broker)
91+
_, isDefaultDealer := rl.dealer.(*dealer)
92+
require.True(t, isDefaultDealer, "expected default *dealer; got %T", rl.dealer)
93+
}

router/router.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,15 +370,24 @@ func (r *router) addRealm(config *RealmConfig) (*realm, error) {
370370
return nil, errors.New("realm already exists: " + string(config.URI))
371371
}
372372

373-
broker, err := newBroker(r.log, config.StrictURI, config.AllowDisclose, r.debug, config.PublishFilterFactory, config.TopicEventHistoryConfigs)
373+
brokerFactory := config.BrokerFactory
374+
if brokerFactory == nil {
375+
brokerFactory = defaultBrokerFactory
376+
}
377+
dealerFactory := config.DealerFactory
378+
if dealerFactory == nil {
379+
dealerFactory = defaultDealerFactory
380+
}
381+
382+
broker, err := brokerFactory(config, r.log, r.debug)
383+
if err != nil {
384+
return nil, err
385+
}
386+
dealer, err := dealerFactory(config, r.log, r.debug)
374387
if err != nil {
375388
return nil, err
376389
}
377-
realm, err := newRealm(
378-
config,
379-
broker,
380-
newDealer(r.log, config.StrictURI, config.AllowDisclose, r.debug),
381-
r.log, r.debug)
390+
realm, err := newRealm(config, broker, dealer, r.log, r.debug)
382391
if err != nil {
383392
return nil, err
384393
}

router/types.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package router
22

3-
import "github.com/gammazero/nexus/v3/wamp"
3+
import (
4+
"github.com/gammazero/nexus/v3/stdlog"
5+
"github.com/gammazero/nexus/v3/wamp"
6+
)
47

58
// Broker handles publish/subscribe and topic-side meta procedures
69
// for a single realm. The default implementation in [broker] is the
@@ -118,3 +121,26 @@ var (
118121
_ Broker = (*broker)(nil)
119122
_ Dealer = (*dealer)(nil)
120123
)
124+
125+
// BrokerFactory constructs the Broker for a given realm. Set it on
126+
// RealmConfig.BrokerFactory to swap in a custom implementation
127+
// (e.g. clustered, metrics-wrapped, forwarding proxy). When unset
128+
// the router uses the default in-process Broker.
129+
type BrokerFactory func(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Broker, error)
130+
131+
// DealerFactory constructs the Dealer for a given realm. Same shape
132+
// as BrokerFactory.
133+
type DealerFactory func(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Dealer, error)
134+
135+
// defaultBrokerFactory builds the in-process actor-loop broker used
136+
// when RealmConfig.BrokerFactory is unset.
137+
func defaultBrokerFactory(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Broker, error) {
138+
return newBroker(logger, cfg.StrictURI, cfg.AllowDisclose, debug,
139+
cfg.PublishFilterFactory, cfg.TopicEventHistoryConfigs)
140+
}
141+
142+
// defaultDealerFactory builds the in-process actor-loop dealer used
143+
// when RealmConfig.DealerFactory is unset.
144+
func defaultDealerFactory(cfg *RealmConfig, logger stdlog.StdLog, debug bool) (Dealer, error) {
145+
return newDealer(logger, cfg.StrictURI, cfg.AllowDisclose, debug), nil
146+
}

0 commit comments

Comments
 (0)