-
Notifications
You must be signed in to change notification settings - Fork 168
/
decoupling_4.go
182 lines (155 loc) · 5.08 KB
/
decoupling_4.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
// -------------------------------------
// Decoupling With Interface Composition
// -------------------------------------
// We change our concrete type System. Instead of using two concrete types Xenia and Pillar, we
// use 2 interface types Puller and Storer. Our concrete type System where we can have concrete
// behaviors is now based on the embedding of 2 interface types. It means that we can inject any
// data, not based on the common DNA but on the data that providing the capability, the behavior
// that we need.
// Now our code can be fully decouplable because any value that implements the Puller interface can be stored
// inside the System (same with Storer interface). We can create multiple Systems and that data can
// be passed in Copy.
// We don't need method here. We just need one function that accept data and its behavior will
// change based on the data we put in.
// Now System is not based on Xenia and Pillar anymore. It is based on 2 interfaces, one that
// stores Xenia and one that stores Pillar. We get the extra layer of decoupling.
// If the system change, no big deal. We replace the system as we need to during the program
// startup.
// We solve this problem. We put this in production. Every single refactoring that we did went into
// production before we did the next one. We keep minimizing technical debt.
// System ps
// -------------------- ---------
// | _________ |-pull | |-pull
// | | | |-store | *System |-store
// | | *Xenia |-pull | | |
// | | | | <------------------ ---------
// | --------- | p | |
// | | | | ----- | * |
// | | * |------- |-> | |-pull | |
// | | | | ----- ---------
// | --------- |
// |
// | __________ |
// | | | |
// | | * Pillar |-store |
// | | | |
// | ---------- | s
// | | | | ----- p s
// | | * |------ |-> | |-store --------- ---------
// | | | | ----- | |-pull | |-store
// | ---------- | | *System | | *System |
// -------------------- | | | |
// A --------- ---------
// | | | | |
// ------------------------------------------| * | ------- | * |
// | | | |
// --------- ---------
package main
import (
"errors"
"fmt"
"io"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
// Data is the structure of the data we are copying.
type Data struct {
Line string
}
// Puller declares behavior for pulling data.
type Puller interface {
Pull(d *Data) error
}
// Storer declares behavior for storing data.
type Storer interface {
Store(d *Data) error
}
// PullStorer declares behaviors for both pulling and storing.
type PullStorer interface {
Puller
Storer
}
// Xenia is a system we need to pull data from.
type Xenia struct {
Host string
Timeout time.Duration
}
// Pull knows how to pull data out of Xenia.
func (*Xenia) Pull(d *Data) error {
switch rand.Intn(10) {
case 1, 9:
return io.EOF
case 5:
return errors.New("Error reading data from Xenia")
default:
d.Line = "Data"
fmt.Println("In:", d.Line)
return nil
}
}
// Pillar is a system we need to store data into.
type Pillar struct {
Host string
Timeout time.Duration
}
// Store knows how to store data into Pillar.
func (*Pillar) Store(d *Data) error {
fmt.Println("Out:", d.Line)
return nil
}
// System wraps Pullers and Stores together into a single system.
type System struct {
Puller
Storer
}
// pull knows how to pull bulks of data from any Puller.
func pull(p Puller, data []Data) (int, error) {
for i := range data {
if err := p.Pull(&data[i]); err != nil {
return i, err
}
}
return len(data), nil
}
// store knows how to store bulks of data from any Storer.
func store(s Storer, data []Data) (int, error) {
for i := range data {
if err := s.Store(&data[i]); err != nil {
return i, err
}
}
return len(data), nil
}
// Copy knows how to pull and store data from any System.
func Copy(ps PullStorer, batch int) error {
data := make([]Data, batch)
for {
i, err := pull(ps, data)
if i > 0 {
if _, err := store(ps, data[:i]); err != nil {
return err
}
}
if err != nil {
return err
}
}
}
func main() {
sys := System{
Puller: &Xenia{
Host: "localhost:8000",
Timeout: time.Second,
},
Storer: &Pillar{
Host: "localhost:9000",
Timeout: time.Second,
},
}
if err := Copy(&sys, 3); err != io.EOF {
fmt.Println(err)
}
}