Package Oriented Design allows a developer to identify where a package belongs inside a Go project and the design guidelines the package must respect. It defines what a Go project is and how a Go project is structured. Finally, it improves communication between team members and promotes clean package design and project architecture that is discussable.
Design Philosophy On Packaging - William Kennedy
Package Oriented Design - William Kennedy
In an interview given to Brian Kernighan by Mihai Budiu in the year 2000, Brian was asked the following question:
“Can you tell us about the worse features of C, from your point of view”?
This was Brian’s response:
“I think that the real problem with C is that it doesn’t give you enough mechanisms for structuring really big programs, for creating "firewalls" within programs so you can keep the various pieces apart. It’s not that you can’t do all of these things, that you can’t simulate object-oriented programming or other methodology you want in C. You can simulate it, but the compiler, the language itself isn’t giving you any help.”
- Packaging directly conflicts with how we have been taught to organize source code in other languages.
- In other languages, packaging is a feature that you can choose to use or ignore.
- You can think of packaging as applying the idea of microservices on a source tree.
- All packages are "first class," and the only hierarchy is what you define in the source tree for your project.
- There needs to be a way to “open” parts of the package to the outside world.
- Two packages can’t cross-import each other. Imports are a one way street.
- To be purposeful, packages must provide, not contain.
- Packages must be named with the intent to describe what it provides.
- Packages must not become a dumping ground of disparate concerns.
- To be usable, packages must be designed with the user as their focus.
- Packages must be intuitive and simple to use.
- Packages must respect their impact on resources and performance.
- Packages must protect the user’s application from cascading changes.
- Packages must prevent the need for type assertions to the concrete.
- Packages must reduce, minimize and simplify its code base.
- To be portable, packages must be designed with reusability in mind.
- Packages must aspire for the highest level of portability.
- Packages must reduce setting policy when it’s reasonable and practical.
- Packages must not become a single point of dependency.
Kit Application
├── CONTRIBUTORS ├── cmd/
├── LICENSE ├── internal/
├── README.md │ └── platform/
├── cfg/ └── vendor/
├── examples/
├── log/
├── pool/
├── tcp/
├── timezone/
├── udp/
└── web/
-
vendor/
Good documentation for thevendor/
folder can be found in this Gopher Academy post by Daniel Theophanes. For the purpose of this post, all the source code for 3rd party packages need to be vendored (or copied) into thevendor/
folder. This includes packages that will be used from the companyKit
project. Consider packages from theKit
project as 3rd party packages. -
cmd/
All the programs this project owns belongs inside thecmd/
folder. The folders undercmd/
are always named for each program that will be built. Use the letterd
at the end of a program folder to denote it as a daemon. Each folder has a matching source code file that contains themain
package. -
internal/
Packages that need to be imported by multiple programs within the project belong inside theinternal/
folder. One benefit of using the nameinternal/
is that the project gets an extra level of protection from the compiler. No package outside of this project can import packages from inside ofinternal/
. These packages are therefore internal to this project only. -
internal/platform/
Packages that are foundational but specific to the project belong in theinternal/platform/
folder. These would be packages that provide support for things like databases, authentication or even marshaling.
Validate the location of a package.
Kit
- Packages that provide foundational support for the different
Application
projects that exist. - logging, configuration or web functionality.
- Packages that provide foundational support for the different
cmd/
- Packages that provide support for a specific program that is being built.
- startup, shutdown and configuration.
internal/
- Packages that provide support for the different programs the project owns.
- CRUD, services or business logic.
internal/platform/
- Packages that provide internal foundational support for the project..
- database, authentication or marshaling.
Validate the dependency choices.
All
- Validate the cost/benefit of each dependency.
- Question imports for the sake of sharing existing types.
- Question imports to others packages at the same level.
- If a package wants to import another package at the same level:
- Question the current design choices of these packages.
- If reasonable, move the package inside the source tree for the package that wants to import it.
- Use the source tree to show the dependency relationships.
internal/
- Packages from these locations CAN’T be imported:
cmd/
- Packages from these locations CAN’T be imported:
internal/platform/
- Packages from these locations CAN’T be imported:
cmd/
internal/
- Packages from these locations CAN’T be imported:
Validate the policies being imposed.
Kit
,internal/platform/
- NOT allowed to set policy about any application concerns.
- NOT allowed to log, but access to trace information must be decoupled.
- Configuration and runtime changes must be decoupled.
- Retrieving metric and telemetry values must be decoupled.
cmd/
,internal/
- Allowed to set policy about any application concerns.
- Allowed to log and handle configuration natively.
Validate how data is accepted/returned.
All
- Validate the consistent use of value/pointer semantics for a given type.
- When using an interface type to accept a value, the focus must be on the behavior that is required and not the value itself.
- If behavior is not required, use a concrete type.
- When reasonable, use an existing type before declaring a new one.
- Question types from dependencies that leak into the exported API.
- An existing type may no longer be reasonable to use.
Validate how errors are handled.
All
- Handling an error means:
- The error has been logged.
- The application is back to 100% integrity.
- The current error is not reported any longer.
- Handling an error means:
Kit
- NOT allowed to panic an application.
- NOT allowed to wrap errors.
- Return only root cause error values.
cmd/
- Allowed to panic an application.
- Wrap errors with context if not being handled.
- Majority of handling errors happen here.
internal/
- NOT allowed to panic an application.
- Wrap errors with context if not being handled.
- Minority of handling errors happen here.
internal/platform/
- NOT allowed to panic an application.
- NOT allowed to wrap errors.
- Return only root cause error values.
Validate testing.
cmd/
- Allowed to use 3rd party testing packages.
- Can have a
test
folder for tests. - Focus more on integration than unit testing.
kit/
,internal/
,internal/platform/
- Stick to the testing package in go.
- Test files belong inside the package.
- Focus more on unit than integration testing.
Validate recovering panics.
cmd/
- Can recover any panic.
- Only if system can be returned to 100% integrity.
kit/
,internal/
,internal/platform/
- Can not recover from panics unless:
- Goroutine is owned by the package.
- Can provide an event to the app about the panic.
- Can not recover from panics unless:
All material is licensed under the Apache License Version 2.0, January 2004.