Skip to content

Commit 63632bc

Browse files
committed
Add map value groups support to dig
1 parent 6fab1b2 commit 63632bc

6 files changed

Lines changed: 530 additions & 2 deletions

File tree

demo/go.mod

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module fx-map-groups-demo
2+
3+
go 1.25.1
4+
5+
require (
6+
go.uber.org/dig v1.19.0
7+
go.uber.org/fx v1.23.0
8+
)
9+
10+
require (
11+
go.uber.org/multierr v1.10.0 // indirect
12+
go.uber.org/zap v1.26.0 // indirect
13+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
14+
)
15+
16+
replace go.uber.org/fx => ../
17+
18+
replace go.uber.org/dig => github.com/jquirke/dig v0.0.0-20250929003136-0b0022552f09

demo/go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/jquirke/dig v0.0.0-20250929003136-0b0022552f09 h1:W7QqWL+tcMYguqdg3YbttY3m20s9d+z1r7lOrL7hzSE=
4+
github.com/jquirke/dig v0.0.0-20250929003136-0b0022552f09/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
8+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
9+
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
10+
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
11+
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
12+
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
13+
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
14+
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
15+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
16+
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
17+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
18+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

demo/map_decoration_demo.go

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"strings"
8+
9+
"go.uber.org/fx"
10+
)
11+
12+
// =====================================================
13+
// DEMO: Map Value Groups + Decoration in Fx
14+
// =====================================================
15+
16+
// Logger interface for different log outputs
17+
type Logger interface {
18+
Log(message string)
19+
Name() string
20+
}
21+
22+
// FileLogger logs to files
23+
type FileLogger struct{ filename string }
24+
25+
func (l *FileLogger) Log(message string) {
26+
fmt.Printf("📁 FileLogger[%s]: %s\n", l.filename, message)
27+
}
28+
29+
func (l *FileLogger) Name() string {
30+
return "file"
31+
}
32+
33+
// ConsoleLogger logs to console
34+
type ConsoleLogger struct{}
35+
36+
func (l *ConsoleLogger) Log(message string) {
37+
fmt.Printf("🖥️ ConsoleLogger: %s\n", message)
38+
}
39+
40+
func (l *ConsoleLogger) Name() string {
41+
return "console"
42+
}
43+
44+
// DatabaseLogger logs to database
45+
type DatabaseLogger struct{}
46+
47+
func (l *DatabaseLogger) Log(message string) {
48+
fmt.Printf("🗄️ DatabaseLogger: %s\n", message)
49+
}
50+
51+
func (l *DatabaseLogger) Name() string {
52+
return "database"
53+
}
54+
55+
// =====================================================
56+
// PROVIDERS
57+
// =====================================================
58+
59+
func ProvideFileLogger() Logger {
60+
return &FileLogger{filename: "app.log"}
61+
}
62+
63+
func ProvideConsoleLogger() Logger {
64+
return &ConsoleLogger{}
65+
}
66+
67+
func ProvideDatabaseLogger() Logger {
68+
return &DatabaseLogger{}
69+
}
70+
71+
// =====================================================
72+
// MAP DECORATION EXAMPLE
73+
// =====================================================
74+
75+
type LoggerDecorationParams struct {
76+
fx.In
77+
// Input: map of loggers by name
78+
Loggers map[string]Logger `group:"loggers"`
79+
}
80+
81+
type LoggerDecorationResult struct {
82+
fx.Out
83+
// Output: enhanced map of loggers
84+
Loggers map[string]Logger `group:"loggers"`
85+
}
86+
87+
// DecorateLoggers demonstrates map decoration - add prefix to all loggers
88+
func DecorateLoggers(params LoggerDecorationParams) LoggerDecorationResult {
89+
fmt.Println("\n🎨 Decorating loggers with [ENHANCED] prefix...")
90+
91+
enhancedLoggers := make(map[string]Logger)
92+
93+
for name, logger := range params.Loggers {
94+
// Wrap each logger with enhancement
95+
enhancedLoggers[name] = &EnhancedLogger{
96+
wrapped: logger,
97+
prefix: "[ENHANCED]",
98+
}
99+
fmt.Printf(" ✨ Enhanced logger: %s\n", name)
100+
}
101+
102+
return LoggerDecorationResult{Loggers: enhancedLoggers}
103+
}
104+
105+
// EnhancedLogger wraps another logger with a prefix
106+
type EnhancedLogger struct {
107+
wrapped Logger
108+
prefix string
109+
}
110+
111+
func (l *EnhancedLogger) Log(message string) {
112+
l.wrapped.Log(fmt.Sprintf("%s %s", l.prefix, message))
113+
}
114+
115+
func (l *EnhancedLogger) Name() string {
116+
return l.wrapped.Name()
117+
}
118+
119+
// =====================================================
120+
// LOGGING SERVICE USING DECORATED MAP
121+
// =====================================================
122+
123+
type LoggingServiceParams struct {
124+
fx.In
125+
126+
// 🎯 This map will contain DECORATED loggers!
127+
LoggerMap map[string]Logger `group:"loggers"`
128+
}
129+
130+
type LoggingService struct {
131+
loggers map[string]Logger
132+
}
133+
134+
func NewLoggingService(params LoggingServiceParams) *LoggingService {
135+
fmt.Printf("\n🔧 LoggingService created with %d decorated loggers\n", len(params.LoggerMap))
136+
137+
// Show what we received
138+
for name := range params.LoggerMap {
139+
fmt.Printf(" 📄 Logger available: %s\n", name)
140+
}
141+
142+
return &LoggingService{loggers: params.LoggerMap}
143+
}
144+
145+
func (s *LoggingService) LogToSpecific(loggerName, message string) error {
146+
logger, exists := s.loggers[loggerName]
147+
if !exists {
148+
return fmt.Errorf("logger %q not found", loggerName)
149+
}
150+
151+
logger.Log(message)
152+
return nil
153+
}
154+
155+
func (s *LoggingService) LogToAll(message string) {
156+
fmt.Println("\n📢 Broadcasting to all loggers:")
157+
for _, logger := range s.loggers {
158+
logger.Log(message)
159+
}
160+
}
161+
162+
func (s *LoggingService) LogToFiltered(message string, filter func(name string) bool) {
163+
fmt.Printf("\n🔍 Logging to filtered loggers (%s):\n", "contains 'log'")
164+
for name, logger := range s.loggers {
165+
if filter(name) {
166+
logger.Log(message)
167+
}
168+
}
169+
}
170+
171+
// =====================================================
172+
// APPLICATION LIFECYCLE
173+
// =====================================================
174+
175+
func RunDecorationDemo(lc fx.Lifecycle, service *LoggingService) {
176+
lc.Append(fx.Hook{
177+
OnStart: func(ctx context.Context) error {
178+
fmt.Println("\n🚀 Starting Map Decoration Demo")
179+
fmt.Println("===================================")
180+
181+
// Test specific logger
182+
fmt.Println("\n🎯 Testing specific logger (decorated):")
183+
service.LogToSpecific("console", "This is a console message")
184+
185+
// Test all loggers (decorated)
186+
service.LogToAll("This message goes to all decorated loggers")
187+
188+
// Test filtered logging (using map keys)
189+
service.LogToFiltered("Filtered message", func(name string) bool {
190+
return strings.Contains(name, "log") // This won't match our current names
191+
})
192+
193+
// Test another filter
194+
fmt.Printf("\n🔍 Logging to filtered loggers (%s):\n", "starts with 'c'")
195+
service.LogToFiltered("Another filtered message", func(name string) bool {
196+
return strings.HasPrefix(name, "c")
197+
})
198+
199+
fmt.Println("\n✅ Map decoration demo completed successfully!")
200+
fmt.Println("\n💡 Key insights:")
201+
fmt.Println(" - Map value groups can be decorated just like slices")
202+
fmt.Println(" - Decorators receive map[string]T and return map[string]T")
203+
fmt.Println(" - All loggers were enhanced with [ENHANCED] prefix")
204+
fmt.Println(" - Map keys (names) are preserved through decoration")
205+
fmt.Println(" - Easy filtering and lookup by name")
206+
207+
return nil
208+
},
209+
})
210+
}
211+
212+
// =====================================================
213+
// MAIN APPLICATION
214+
// =====================================================
215+
216+
func main() {
217+
app := fx.New(
218+
// Provide loggers with names and groups
219+
fx.Provide(
220+
fx.Annotate(
221+
ProvideFileLogger,
222+
fx.ResultTags(`name:"file" group:"loggers"`),
223+
),
224+
fx.Annotate(
225+
ProvideConsoleLogger,
226+
fx.ResultTags(`name:"console" group:"loggers"`),
227+
),
228+
fx.Annotate(
229+
ProvideDatabaseLogger,
230+
fx.ResultTags(`name:"database" group:"loggers"`),
231+
),
232+
),
233+
234+
// 🎨 DECORATE the logger map - add enhancements
235+
fx.Decorate(DecorateLoggers),
236+
237+
// Provide the logging service (receives decorated loggers)
238+
fx.Provide(NewLoggingService),
239+
240+
// Register the demo lifecycle hook
241+
fx.Invoke(RunDecorationDemo),
242+
243+
// Suppress logs for cleaner demo output
244+
fx.NopLogger,
245+
)
246+
247+
fmt.Println("🎨 Fx Map Value Groups + Decoration Demo")
248+
fmt.Println("==========================================")
249+
fmt.Println("This demo shows map decoration with our new feature!")
250+
251+
if err := app.Start(context.Background()); err != nil {
252+
log.Fatal(err)
253+
}
254+
255+
if err := app.Stop(context.Background()); err != nil {
256+
log.Fatal(err)
257+
}
258+
}

0 commit comments

Comments
 (0)