Splitting client/server code
Asked Answered
B

1

1

I'm developing a client/server application in golang, and there are certain logical entities that exist both on client and server(the list is limited)

I would like to ensure certain code for this entities is included ONLY in the server part but NOT in the client(wise versa is nice, but not so important).

The naive thought would be to rely on dead code elimination, but from my brief research it's not a reliable way to handle the task... go build simply won't eliminate dead code from the fact that it may have been used via reflection(nobody cares that it wasn't and there is no option to tune this)

More solid approach seems to be splitting code in different packages and import appropriately, this seems reliable but over-complicates the code forcing you to physically split certain entities between different packages and constantly keep this in mind...

And finally there are build tags allowing to have multiple files under the same package built conditionally for client and server

The motivation with using build tags is that I want to keep code as clean as possible without introducing any synthetic entities

Use case: there are certain cryptography routines, client works with public key, server operates with private... Code logically belongs to the same entity

What option would you choose and why?

Blindstory answered 10/8, 2016 at 13:36 Comment(0)
B
4

This "dead code elimination" is already done–partly–by the go tool. The go tool does not include everything from imported packages, only what is needed (or more precisely: it excludes things that it can prove unreachable).

For example this application

package main; import _ "fmt"; func main() {}

results in almost 300KB smaller executable binary (on windows amd64) compared to the following:

package main; import "fmt"; func main() {fmt.Println()}

Excludable things include functions, types and even unexported and exported variables. This is possible because even with reflection you can't call a function or "instantiate" types or refer to package variables just by having their names as a string value. So maybe you shouldn't worry about it that much.

Edit: With Go 1.7 released, it is even better: read blog post: Smaller Go 1.7 binaries

So if you design your types and functions well, and you don't create "giant" registries where you enumerate functions and types (which explicitly generates references to them and thus renders them unexcludable), compiled binaries will only contain what is actually used from imported packages.

I would not suggest to use build tags for this kind of problem. By using them, you'll have an extra responsibility to maintain package / file dependencies yourself which otherwise is done by the go tool.

You should not design and separate code into packages to make your output executables smaller. You should design and separate code into packages based on logic.

I would go with separating stuffs into packages when it is really needed, and import appropriately. Because this is really what you want: some code intended only for the client, some only for the server. You may have to think a little more during your design and coding phase, but at least you will see the result (what actually belongs / gets compiled into the client and into the server).

Bovill answered 10/8, 2016 at 14:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.