Skip to content

Commit fa0f0d4

Browse files
committed
d2: service_services format mode
1 parent a5314d1 commit fa0f0d4

9 files changed

Lines changed: 384 additions & 160 deletions

File tree

cmd/messageflow/commands/docs/docs.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ func (c *Command) generateDocumentation(
154154

155155
serviceDiagrams := make(map[string][]byte)
156156
for _, service := range schema.Services {
157-
diagram, err := c.generateServiceChannelsDiagram(ctx, schema, target, service.Name)
157+
diagram, err := c.generateServiceServicesDiagram(ctx, schema, target, service.Name)
158158
if err != nil {
159-
return fmt.Errorf("error generating service channels diagram for %s: %w", service.Name, err)
159+
return fmt.Errorf("error generating service services diagram for %s: %w", service.Name, err)
160160
}
161161
serviceDiagrams[service.Name] = diagram
162162
}
@@ -210,25 +210,25 @@ func (c *Command) generateContextDiagram(
210210
return diagram, nil
211211
}
212212

213-
func (c *Command) generateServiceChannelsDiagram(
213+
func (c *Command) generateServiceServicesDiagram(
214214
ctx context.Context,
215215
schema messageflow.Schema,
216216
target messageflow.Target,
217217
serviceName string,
218218
) ([]byte, error) {
219219
formatOpts := messageflow.FormatOptions{
220-
Mode: messageflow.FormatModeServiceChannels,
220+
Mode: messageflow.FormatModeServiceServices,
221221
Service: serviceName,
222222
}
223223

224224
formattedSchema, err := target.FormatSchema(ctx, schema, formatOpts)
225225
if err != nil {
226-
return nil, fmt.Errorf("error formatting service channels schema: %w", err)
226+
return nil, fmt.Errorf("error formatting service services schema: %w", err)
227227
}
228228

229229
diagram, err := target.RenderSchema(ctx, formattedSchema)
230230
if err != nil {
231-
return nil, fmt.Errorf("error rendering service channels diagram: %w", err)
231+
return nil, fmt.Errorf("error rendering service services diagram: %w", err)
232232
}
233233

234234
return diagram, nil

messageflow.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
FormatModeContextServices = FormatMode("context_services")
2222
FormatModeServiceChannels = FormatMode("service_channels")
2323
FormatModeChannelServices = FormatMode("channel_services")
24+
FormatModeServiceServices = FormatMode("service_services")
2425
)
2526

2627
type FormatOptions struct {

target/d2/d2.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ var (
3232

3333
//go:embed templates/context_services.tmpl
3434
contextServicesTemplateFS embed.FS
35+
36+
//go:embed templates/service_services.tmpl
37+
serviceServicesTemplateFS embed.FS
3538
)
3639

3740
// Ensure Target implements messageflow interfaces.
@@ -44,6 +47,7 @@ type Target struct {
4447
serviceChannelsTemplate *template.Template
4548
channelServicesTemplate *template.Template
4649
contextServicesTemplate *template.Template
50+
serviceServicesTemplate *template.Template
4751
renderOpts *d2svg.RenderOpts
4852
compileOpts *d2lib.CompileOptions
4953
}
@@ -87,6 +91,11 @@ func NewTarget(opts ...TargetOpt) (*Target, error) {
8791
return nil, fmt.Errorf("parsing context services template: %w", err)
8892
}
8993

94+
serviceServicesTemplate, err := template.ParseFS(serviceServicesTemplateFS, "templates/service_services.tmpl")
95+
if err != nil {
96+
return nil, fmt.Errorf("parsing service services template: %w", err)
97+
}
98+
9099
ruler, err := textmeasure.NewRuler()
91100
if err != nil {
92101
return nil, fmt.Errorf("creating ruler: %w", err)
@@ -100,6 +109,7 @@ func NewTarget(opts ...TargetOpt) (*Target, error) {
100109
serviceChannelsTemplate: serviceChannelsTemplate,
101110
channelServicesTemplate: channelServicesTemplate,
102111
contextServicesTemplate: contextServicesTemplate,
112+
serviceServicesTemplate: serviceServicesTemplate,
103113
renderOpts: &d2svg.RenderOpts{
104114
Pad: go2.Pointer(int64(5)),
105115
},
@@ -140,6 +150,11 @@ type contextServicesPayload struct {
140150
Connections []connection
141151
}
142152

153+
type serviceServicesPayload struct {
154+
MainService messageflow.Service
155+
NeighborServices []messageflow.Service
156+
}
157+
143158
type connection struct {
144159
From string
145160
To string
@@ -180,11 +195,19 @@ func (t *Target) FormatSchema(
180195
if err != nil {
181196
return messageflow.FormattedSchema{}, fmt.Errorf("executing channel services template: %w", err)
182197
}
198+
case messageflow.FormatModeServiceServices:
199+
payload := prepareServiceServicesPayload(s, opts.Service)
200+
201+
err := t.serviceServicesTemplate.Execute(&buf, payload)
202+
if err != nil {
203+
return messageflow.FormattedSchema{}, fmt.Errorf("executing service services template: %w", err)
204+
}
183205
default:
184206
return messageflow.FormattedSchema{}, messageflow.NewUnsupportedFormatModeError(opts.Mode, []messageflow.FormatMode{
185207
messageflow.FormatModeServiceChannels,
186208
messageflow.FormatModeChannelServices,
187209
messageflow.FormatModeContextServices,
210+
messageflow.FormatModeServiceServices,
188211
})
189212
}
190213

@@ -420,3 +443,49 @@ func findServiceByName(s messageflow.Schema, name string) messageflow.Service {
420443
}
421444
return messageflow.Service{}
422445
}
446+
447+
func prepareServiceServicesPayload(s messageflow.Schema, serviceName string) serviceServicesPayload {
448+
var mainService messageflow.Service
449+
if serviceName == "" && len(s.Services) == 1 {
450+
mainService = s.Services[0]
451+
} else {
452+
for _, service := range s.Services {
453+
if service.Name == serviceName {
454+
mainService = service
455+
break
456+
}
457+
}
458+
}
459+
460+
neighborServices := make([]messageflow.Service, 0)
461+
neighborServiceMap := make(map[string]bool)
462+
463+
mainServiceChannels := make(map[string]bool)
464+
for _, op := range mainService.Operation {
465+
mainServiceChannels[op.Channel.Name] = true
466+
}
467+
468+
for _, service := range s.Services {
469+
if service.Name == mainService.Name {
470+
continue
471+
}
472+
473+
hasCommonChannel := false
474+
for _, op := range service.Operation {
475+
if mainServiceChannels[op.Channel.Name] {
476+
hasCommonChannel = true
477+
break
478+
}
479+
}
480+
481+
if hasCommonChannel && !neighborServiceMap[service.Name] {
482+
neighborServices = append(neighborServices, service)
483+
neighborServiceMap[service.Name] = true
484+
}
485+
}
486+
487+
return serviceServicesPayload{
488+
MainService: mainService,
489+
NeighborServices: neighborServices,
490+
}
491+
}

target/d2/d2_test.go

Lines changed: 123 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -114,27 +114,15 @@ func TestFormatSchema(t *testing.T) {
114114
Channel: messageflow.Channel{
115115
Name: "user.info.request",
116116
Message: messageflow.Message{
117-
Name: "UserInfoRequest",
118-
Payload: `{
119-
"user_id": "string[uuid]"
120-
}`,
117+
Name: "UserInfoRequest",
118+
Payload: `{"user_id": "string[uuid]"}`,
121119
},
122120
},
123121
Reply: &messageflow.Channel{
124122
Name: "user.info.request",
125123
Message: messageflow.Message{
126-
Name: "UserInfoReply",
127-
Payload: `{
128-
"email": "string[email]",
129-
"error": {
130-
"code": "string",
131-
"message": "string"
132-
},
133-
"language": "string",
134-
"name": "string",
135-
"timezone": "string",
136-
"user_id": "string[uuid]"
137-
}`,
124+
Name: "UserInfoReply",
125+
Payload: `{"email": "string[email]", "name": "string"}`,
138126
},
139127
},
140128
},
@@ -143,20 +131,8 @@ func TestFormatSchema(t *testing.T) {
143131
Channel: messageflow.Channel{
144132
Name: "notification.analytics",
145133
Message: messageflow.Message{
146-
Name: "AnalyticsEvent",
147-
Payload: `{
148-
"event_id": "string[uuid]",
149-
"event_type": "string[enum:notification_sent,notification_opened,notification_clicked]",
150-
"metadata": {
151-
"environment": "string[enum:development,staging,production]",
152-
"platform": "string[enum:ios,android,web]",
153-
"source": "string[enum:mobile,web,api]",
154-
"version": "string"
155-
},
156-
"notification_id": "string[uuid]",
157-
"timestamp": "string[date-time]",
158-
"user_id": "string[uuid]"
159-
}`,
134+
Name: "AnalyticsEvent",
135+
Payload: `{"event_type": "string", "user_id": "string[uuid]"}`,
160136
},
161137
},
162138
},
@@ -285,3 +261,120 @@ func TestFormatSchemaChannelServicesWithOmitPayloads(t *testing.T) {
285261
assert.Contains(t, actualOmittedStr, "Notification Service")
286262
assert.Contains(t, actualOmittedStr, "User Service")
287263
}
264+
265+
func TestFormatSchemaServiceServices(t *testing.T) {
266+
t.Parallel()
267+
268+
schema := messageflow.Schema{
269+
Services: []messageflow.Service{
270+
{
271+
Name: "Notification Service",
272+
Description: "Handles notification operations",
273+
Operation: []messageflow.Operation{
274+
{
275+
Action: messageflow.ActionReceive,
276+
Channel: messageflow.Channel{
277+
Name: "notification.preferences.get",
278+
Message: messageflow.Message{
279+
Name: "PreferencesRequest",
280+
Payload: `{"user_id": "string[uuid]"}`,
281+
},
282+
},
283+
Reply: &messageflow.Channel{
284+
Name: "notification.preferences.get",
285+
Message: messageflow.Message{
286+
Name: "PreferencesReply",
287+
Payload: `{"preferences": {"email_enabled": "boolean"}}`,
288+
},
289+
},
290+
},
291+
{
292+
Action: messageflow.ActionSend,
293+
Channel: messageflow.Channel{
294+
Name: "user.info.request",
295+
Message: messageflow.Message{
296+
Name: "UserInfoRequest",
297+
Payload: `{"user_id": "string[uuid]"}`,
298+
},
299+
},
300+
Reply: &messageflow.Channel{
301+
Name: "user.info.request",
302+
Message: messageflow.Message{
303+
Name: "UserInfoReply",
304+
Payload: `{"email": "string[email]", "name": "string"}`,
305+
},
306+
},
307+
},
308+
{
309+
Action: messageflow.ActionSend,
310+
Channel: messageflow.Channel{
311+
Name: "notification.analytics",
312+
Message: messageflow.Message{
313+
Name: "AnalyticsEvent",
314+
Payload: `{"event_type": "string", "user_id": "string[uuid]"}`,
315+
},
316+
},
317+
},
318+
},
319+
},
320+
{
321+
Name: "User Service",
322+
Description: "Manages user information",
323+
Operation: []messageflow.Operation{
324+
{
325+
Action: messageflow.ActionReceive,
326+
Channel: messageflow.Channel{
327+
Name: "user.info.request",
328+
Message: messageflow.Message{
329+
Name: "UserInfoRequest",
330+
Payload: `{"user_id": "string[uuid]"}`,
331+
},
332+
},
333+
Reply: &messageflow.Channel{
334+
Name: "user.info.request",
335+
Message: messageflow.Message{
336+
Name: "UserInfoReply",
337+
Payload: `{"email": "string[email]", "name": "string"}`,
338+
},
339+
},
340+
},
341+
},
342+
},
343+
{
344+
Name: "Analytics Service",
345+
Description: "Tracks analytics events",
346+
Operation: []messageflow.Operation{
347+
{
348+
Action: messageflow.ActionReceive,
349+
Channel: messageflow.Channel{
350+
Name: "notification.analytics",
351+
Message: messageflow.Message{
352+
Name: "AnalyticsEvent",
353+
Payload: `{"event_type": "string", "user_id": "string[uuid]"}`,
354+
},
355+
},
356+
},
357+
},
358+
},
359+
},
360+
}
361+
362+
ctx := context.Background()
363+
364+
target, err := NewTarget()
365+
require.NoError(t, err)
366+
367+
actual, err := target.FormatSchema(ctx, schema, messageflow.FormatOptions{
368+
Mode: messageflow.FormatModeServiceServices,
369+
Service: "Notification Service",
370+
})
371+
require.NoError(t, err)
372+
373+
actualStr := string(actual.Data)
374+
assert.Contains(t, actualStr, "Notification Service")
375+
assert.Contains(t, actualStr, "User Service")
376+
assert.Contains(t, actualStr, "notification.preferences.get")
377+
assert.Contains(t, actualStr, "user.info.request")
378+
assert.Contains(t, actualStr, "notification.analytics")
379+
assert.Contains(t, actualStr, "Analytics Service")
380+
}

target/d2/templates/channel_services.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ senders -> '{{.Channel}}'
8383

8484
{{- if .Receivers }}
8585
{{- if .ReplyMessage }}
86-
'{{.Channel}}' -> repliers
86+
'{{.Channel}}' <- repliers
8787
{{- else }}
8888
'{{.Channel}}' -> receivers
8989
{{- end }}

target/d2/templates/service_channels.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Reply({{.Reply.Message.Name}}):
107107
'{{.Name}}' -> 'Send To'
108108
{{- end }}
109109
{{- if $hasReplyTo }}
110-
'Reply To' -> '{{.Name}}'
110+
'Reply To' <- '{{.Name}}'
111111
{{- end }}
112112
{{- if $hasRequestFrom }}
113113
'{{.Name}}' -> 'Request From'

0 commit comments

Comments
 (0)