-
Notifications
You must be signed in to change notification settings - Fork 0
/
aggregateMetadata.go
182 lines (154 loc) · 5.43 KB
/
aggregateMetadata.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package eventuate
import (
"fmt"
loglib "github.com/eventuate-clients/eventuate-client-golang/logger"
"log"
"reflect"
"regexp"
"strings"
"sync"
)
type AggregateMetadata struct {
Type reflect.Type
UnderlyingType reflect.Type
EntityTypeName string
EventMethods []string
CommandMethods []string
ll loglib.LogLevelEnum
lg loglib.Logger
lmu sync.Mutex
commandMethodsMap map[string]reflect.Method
eventMethodsMap map[reflect.Type]reflect.Method
//eventTypesMap TypeHintMapper
newInstance func() (*EntityMetadata, error)
}
func (meta *AggregateMetadata) String() string {
return fmt.Sprintf("EntityTypeName: %s, Type: %s, Event methods: [%v], Command methods: [%v]",
meta.EntityTypeName,
meta.Type.Name(),
meta.EventMethods,
meta.CommandMethods)
}
func CreateAggregateMetadata(newInstance interface{}, entityTypeName string) (*AggregateMetadata, error) {
eventNamePattern := regexp.MustCompile(`^Apply(\w+)?Event$`)
commandNamePattern := regexp.MustCompile(`^Process(\w+)?Command`)
defer func() {
if r := recover(); r != nil {
err := AppError("Recovered (%v)", r)
log.Fatal(err)
}
}()
funcValue := reflect.ValueOf(newInstance)
funcType := funcValue.Type()
if funcType.Kind() != reflect.Func || funcType.NumIn() != 0 || funcType.NumOut() != 1 {
return nil, AppError("%s (%s)",
"`newInstance` signature mismatch",
"newInstance must be a function that takes no arguments and produces exactly one value")
}
funcTypeOut := funcType.Out(0)
underlyingFuncTypeOut := funcTypeOut
if funcTypeOut.Kind() == reflect.Ptr {
underlyingFuncTypeOut = funcTypeOut.Elem()
}
methodsCount := funcTypeOut.NumMethod()
methodsMap := make(map[string]reflect.Method)
methodNames := make([]string, methodsCount)
methods := make([]reflect.Method, methodsCount)
commandMethods := make(map[string]reflect.Method)
eventMethods := make(map[reflect.Type]reflect.Method)
//eventTypes := make(map[string]reflect.Type)
for i := 0; i < methodsCount; i++ {
method := funcTypeOut.Method(i)
methods = append(methods, method)
methodName := method.Name
methodNames = append(methodNames, methodName)
methodsMap[methodName] = method
var commandType reflect.Type
if commandNamePattern.MatchString(methodName) {
// dealing with Command
commandCoreName := commandNamePattern.ReplaceAllString(methodName, `$1`)
isInParamsOk := method.Type.NumIn() == 2
isOutParamsOk := method.Type.NumOut() == 1
isGenericName := len(commandCoreName) == 0
isMethodOk := isInParamsOk && isOutParamsOk
isValidCommand := false
commandKey := ""
if isMethodOk {
if isGenericName {
isValidCommand = true
} else {
commandType = method.Type.In(1)
underlyingType := getUnderlyingType(commandType)
isValidCommand = strings.HasSuffix(underlyingType.Name(), "Command")
commandKey = underlyingType.Name()
}
}
_, hasDuplicateKey := commandMethods[commandKey]
if isMethodOk && isValidCommand && !hasDuplicateKey {
commandMethods[commandKey] = method
} else {
if hasDuplicateKey {
return nil, AppError("Same command in several methods. func (recv %s) %s(command %s).",
funcTypeOut,
methodName,
commandType)
}
return nil, AppError("Signature mismatch. func (recv %s) %s(command %s). Ensure the argument `command` ends with 'Command', and the method returns a single value",
funcTypeOut,
methodName,
commandType)
}
}
if eventNamePattern.MatchString(methodName) {
// dealing with Event
eventCoreName := eventNamePattern.ReplaceAllString(methodName, `$1`)
classDef, classOk := getVerifiedArgumentType(method, eventCoreName)
//if classOk && len(eventCoreName) > 0 {
// //underlyingType := getUnderlyingType(classDef)
// //classOk = hasProp(underlyingType, "EventType", "eventType") &&
// // hasProp(underlyingType, "EventData", "eventData")
//}
classOk = classOk && (method.Type.NumOut() == 1)
if classOk {
eventMethods[classDef] = method
//eventTypes[eventCoreName] = classDef
} else {
return nil, AppError("Signature mismatch. func (recv %s) %s(Event %v). Ensure the argument `Event` is a structure whose name ends with 'Event', and the method returns a single value",
funcTypeOut,
methodName,
classDef)
}
}
}
meta := &AggregateMetadata{
Type: funcTypeOut,
UnderlyingType: underlyingFuncTypeOut,
EntityTypeName: entityTypeName,
EventMethods: Filter(methodNames, eventNamePattern.MatchString),
CommandMethods: Filter(methodNames, commandNamePattern.MatchString),
eventMethodsMap: eventMethods,
//eventTypesMap: TypeHintMapper,
commandMethodsMap: commandMethods,
newInstance: nil}
meta.newInstance = createConstructor(funcValue, meta)
return meta, nil
}
func createConstructor(funcValue reflect.Value, meta *AggregateMetadata) func() (*EntityMetadata, error) {
return func() (*EntityMetadata, error) {
results := funcValue.Call([]reflect.Value{})
if len(results) == 2 {
maybeError := results[1].Interface()
if !reflect.DeepEqual(maybeError, reflect.Zero(results[1].Type()).Interface()) {
return nil, AppError("Constructor for type %v returned with Error. (%v)",
meta.Type,
maybeError)
}
}
tmp := results[0].Interface()
return &EntityMetadata{
EntityTypeName: meta.EntityTypeName,
HasEntity: true,
EntityInstance: tmp,
metadata: meta}, nil
}
}