github.com/altipla-consulting/ravendb-go-client@v0.1.3/porting_notes.md (about) 1 This document describes how and why Go port deviates from Java codebase. 2 3 Go is a statically typed language without generics. 4 5 That means that code patterns that work well in a dynamically typed language (Python, 6 Ruby) or a statically typed language with generics (Java, C#) are akward or 7 impossible when transliterated to Go. 8 9 Go library follows structure and terminology of Python and Java libraries but 10 sometimes it must diverge. 11 12 To make future maintenance easier, this documents implementation choices and why 13 they were made. 14 15 ## Java OOP vs. Go 16 17 Java has inheritance with ability to make some functions virtual in Base class and over-ride them in Derived classes. 18 19 Go only has embedding. A Derived struct can embed Base struct and will "inherit" fields and methods of the Base. 20 21 Go has interfaces which allows virtual functions. You can define an interface Foo, implement it by Bar1 and Bar2 structs. Function that takes Foo as an argument can receive Bar1 and Bar2 and will call the right virtual functions on them. 22 23 One might think that embedding + interface can be used to implement Java inheritance: 24 * define interface Foo 25 * have Base struct implement it 26 * embed Base in Derived struct 27 * over-write some interface (virtual) functions in Derived 28 29 There is a subtle but important difference. 30 31 if `Base.virt()` is a virtual function over-written by `Derived.virt()`, a function implemented on `Base` class will call `Derived.virt()` if the object is actually `Derived`. 32 33 It makes sense within the design. `Base` is embedded in `Derived`. `Derived` has access to `Base` but not the other way around. `Base` has no way to call code in `Derived`. 34 35 To put it differently: 36 * in Java, a virtual table is part of class and carried by Object itself. Virtual calls can therefore always be resolved 37 * in Go, a virtual table is carried as a separate interface type, which combines a value and its type information (including virtual table). We can only resolve virtual calls from interface type. Once virtual method is resolved it operates on a concrete type and only has access to that type 38 39 For example, in Java `RavenCommand.processResponse` calls virtual functions of derived classes. That couldn't be done in Go. 40 41 ## Comands and RequestExecutor 42 43 A RavenCommand encapsulates a unique request/response interaction with 44 the server over HTTP. RavenCommand constructs HTTP request from command-specific 45 arguments and parses JSON response into a command-specific return value. 46 47 In Python, the pattern is: 48 49 ```python 50 database_names = store.maintenance.server.send(GetDatabaseNamesOperation(0, 3)) 51 ``` 52 53 As everything in Python, the result is dynamically typed, but the caller knows 54 what to expect. 55 56 In Java, the pattern is: 57 58 ```java 59 GetDatabaseNamesOperation databaseNamesOperation = new GetDatabaseNamesOperation(0, 20); 60 RavenCommand<string[]> command = databaseNamesOperation.GetCommand(conventions); 61 string[] databaseNames = executor.Execute(command); 62 ``` 63 64 Result is statically typed because we can encode type of the result via generic 65 parametrization of RavenCommand. 66 67 Go has no generics so we can't have `RavenCommand` sublclasses specialized by return type. 68 69 We could mimic dynamic typing of Python and define interface for `RavenCommand` 70 which returns a parsed result for each command as `interface{}` but that would 71 be bad Go code. We want a staticly typed result. 72 73 So we invert the logic. 74 75 `RavenCommand` is just a struct that holds all information needed to construct 76 an HTTP request to the server. 77 78 For each command we have `NewFooCommand` (e.g. `NewGetClusterTopologyCommand`) 79 which creates `RavenCommand` from command-specific arguments. 80 81 For each command we also have `ExecuteFooCommand(executor, cmd)` 82 (e.g. `ExecuteGetClusterTopologyCommand`) which takes an abstract executor 83 that takes `RavenCommand`, does HTTP request and returns HTTP response. 84 85 `ExecuteFooCommadn` returns a command-specific result based on parsing JSON 86 response from the server. 87 88 The simplest implemention of executor runs the code against a single server. 89 90 Another implementation will adapt `RequestsExecutor` logic. 91 92 ## How I port Java tests 93 94 Java runs tests in a fixed but unpredictable order. For easier debugging (e.g. when comparing recorded HTTP traffic) I want Go tests to run in the same order as Java tests. 95 96 I instrument Java test with System.out.println() to print name of executed test. 97 98 For e.g. `TrackEntityTest.java` I create `track_entity_test.go` (files that end with `_test.go` are only compiled when running tests). 99 100 `TracEntityTest` Java class is `TestTrackEntity()`. Each function in the form `Test*(*testing.T)` is a unique test for `go test`. 101 102 Each Java class method becomes a function e.g. `TrackEntityTest.deletingEntityThatIsNotTrackedShouldThrow` => `trackEntityTest_deletingEntityThatIsNotTrackedShouldThrow`. 103 104 Usually in Go each function would be a separate test function but to have control over test invocation order, they're part of `TestTrackEntity` test function. 105 106 To get HTTP logs for Java I add the test to `run_tests.go` to log to `trace_track_entity_java.txt` and call `./run_java_tests.sh`. 107 108 I port the tests and run them, also capturing HTTP logs to `trace_track_entity_go.txt`. 109 110 ## How I debug tests 111 112 I use Visual Studio Code as an editor. 113 114 It's Go extension has a support for running individual tests (see https://www.notion.so/Debugging-tests-0f731a22d6154a7ba38a8503227b593d) so I set the desired breakpoints to step through the code and use that. 115 116 Other editors also support Go but I'm not familiar with them. 117 118 ## Why no sub-packages? 119 120 Java code is split into multiple packages/sub-directories. Why not mimic that? 121 122 Go packages have restrictions: they can't have circular references. 123 124 Java code has lots of mutual-references between packages so it's impossible to 125 replicate its structure in Go. 126 127 ## Enums 128 129 Go doesn't have enumes. 130 131 Java enums are represented as constants. Those that are `@UseSharpEnum` are typed as string. In other words, this: 132 133 ```java 134 @UseSharpEnum 135 public enum FieldStorage { 136 YES, 137 NO 138 } 139 ``` 140 141 Is turned into this: 142 ```go 143 type FieldStorage = string 144 145 const ( 146 FieldStorage_YES = "Yes" 147 FieldStorage_NO = "No" 148 ) 149 ``` 150 151 ## Statically ensuring a type implements an interface 152 153 Go implements duck-typing of interfaces i.e. a struct doesn't have to declare 154 that it implements an interface. That opens up a possibility of not 155 implementing an interface correctly. 156 157 A simple trick to ensure that a struct implements interface: 158 159 ```go 160 var _ IVoidMaintenanceOperation = &PutClientConfigurationOperation{} 161 ``` 162 163 ## toString() 164 165 Go has a `fmt.Stringer` interface with `String()` method but basic types (`int`, `float64` etc.) don't implement it (and we can't add methods to existing types). 166 167 Instead of `Object.toString` we can use `fmt.Sprintf("%#v", object)` which will use `String()` method if available and will format known types (including basic types) as their Go literal representation (most importantly it quotes strings so string `foo` has literal representation as `"foo"`). 168 169 To avoid quoting strings, use `%v` or `%s`. 170 171 ## `id` vs. `ID` 172 173 In Java, the name of id property is `id`. 174 175 In Go publicly accessible properties have to start with capital letter so it would have to be `Id`. Additionally the Go naming rule for abbreviations is all capitalized i.e. `ID`. 176 177 ## `CleanCloseable` => `io.Closer` 178 179 Go standard library has a `io.Closer` interface which is the same as as `CleanCloseable`. 180 181 ## interface vs. concrete types 182 183 In Go the only reason to define an interface is if there is more than one implementation. 184 185 In Java interfaces are sometimes used to limit API exposed to the user of the library. 186 Due to Java's per-class access control if the library uses an object method, it must be 187 public and therefore visible to clients of the library. 188 189 In Go we don't need to do that because access control is per-package. 190 191 Therefore many such interfaces are removed and we instead expose concrete types in the API. 192 193 ## managing callbacks 194 195 Go doesn't allow comparing functions so for the purpose of removing them, we need to identify them somehow. 196 197 I chose the simplest solution: they are identified by the index in the array of callbacks. 198 199 Function that adds a callback returns the index. 200 201 To make the index stable, we never shrink the arrays. Removing callback from the array is `a[idx] = nil`. 202 203 This assumes that there is no big churn of adding/removing callbacks, which would grow callback arrays infnitely. 204 205 If churn does happen, we can change things to use a unique id and store callbacks as a pair of (id, function). 206 207 ## random iteration over maps 208 209 In Java iterating over dictionaries has a stable order. 210 211 In Go, iteration over maps (`for k, v := range m`) has intentionally random order. 212 213 There are code paths in Java code that relay on stable iteration order. 214 215 To implement that in Go, we first collect keys of a map, sort them and then iterate based on order of sorted keys. 216 217 ## controlling logging && debugging 218 219 You can set the following envirnment variables to "true" to enable more logging: 220 * `LOG_HTTP_REQUEST_SUMMARY` : logs summary of http request 221 * `LOG_FAILED_HTTP_REQUESTS` : when a request fails (either a network error or returns status >= 400), will print request and response, including their bodies 222 * `LOG_RAVEN_SERVER` : will set logging level of RavenDB server to 'Information' and will print server output to stdout 223 * `ENABLE_FLAKY_TESTS` : runs flaky tests (those that sometimes fail and sometimes succeed) 224 * `VERBOSE_LOG` : prints additional ad-hoc logging made via `dbg()` call. This is meant for adding temporary logging 225 226 ## branching strategy to support go modules 227 228 In Go 1.11 go added support for go modules which is the official way to manage module dependencies going forward. 229 230 To support Go modules and greater Go ecosystem, we need the following branching strategy: 231 * master branch has the latest code 232 * branches like v4.0 and v4.1 are for maintenance, bug fix work for older releases 233 * to make a release, we use tags e.g. v4.1.1, v4.1.2 etc. (Go modules use semantic versioning) 234 * go modules picks the latest tag (e.g. v4.2.2 will be picked over v4.2.1 or v4.1.5)