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)