Gopher says
Think of packages as rooms in a train station. Each room has a clear purpose, a name on the door, and rules about who can enter. In Go, every directory is a package, and capitalization decides what’s public.
In baton-junction, pkg/connector/ holds the sync logic, pkg/client/
handles all HTTP communication, and cmd/baton-junction/ is the entry point.
Exported types like Client cross package boundaries; helpers like doGet
stay internal.
Package Declarations
Every Go file starts with package name. All files in the same directory must use the same name:
baton-junction/
cmd/baton-junction/ package main
pkg/client/ package client
pkg/config/ package config
pkg/connector/ package connector Files in the same package share a namespace - users.go can call parsePageToken from helpers.go without importing it.
Exported vs Unexported
Think of your package as a storefront: you decide what goes in the window. Exported names form your public API; unexported names stay hidden inside. Go makes this decision automatic based on one rule.
The rule is simple: uppercase = exported (public), lowercase = unexported (private).
type Client struct {
httpClient *uhttp.BaseHttpClient
baseURL string
}
func New(...) (*Client, error)
func (c *Client) doGet(...) From baton-junction’s connector package:
| Exported (public API) | Unexported (internal) |
|---|---|
Connector, New | userBuilder, groupBuilder |
Client, User, Role | doGet, doPut, doDelete |
GetUsers, CreateUser | parsePageToken, mapUserStatus |
Config | defaultPageSize, memberEntitlement |
The compiler enforces this - accessing a lowercase name from another package is a compile error, not just a convention.
Import Groups
Go cares about import organization because it improves readability at a glance. You can always tell where a dependency comes from: standard library, your own code, or external packages. This three-group convention is idiomatic and keeps imports easy to scan.
Go imports follow a three-group convention separated by blank lines:
import (
"context"
"fmt"
"example/baton-junction/pkg/client"
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
) | Group | Contents | How to Recognize |
|---|---|---|
| 1 | Standard library | No dots: "fmt", "context" |
| 2 | Your packages | Starts with module path: "example/baton-junction/..." |
| 3 | External deps | Starts with a domain: "github.com/..." |
Import Aliases
This situation comes up when you have internal config (pkg/config) and SDK config (github.com/.../config), or when using multiple versions of the same package. When two packages share a name, alias one:
appConfig "example/baton-junction/pkg/config"
"github.com/conductorone/baton-sdk/pkg/config"
Also useful for clarity: v2 "github.com/.../connector/v2" is more readable than just v2.
Project Layout
Go projects follow a common layout so that any Go developer can jump in and find things quickly. The cmd/ + pkg/ pattern separates entry points from reusable logic.
baton-junction follows Go’s conventional cmd/ + pkg/ layout:
Import arrows only flow downward
Go forbids circular imports - if A imports B, B cannot import A.
The go.mod file sets the root for all import paths:
module example/baton-junction
go 1.25.2Directory pkg/client/ becomes import path "example/baton-junction/pkg/client". In baton connectors, the module path is a GitHub URL like github.com/conductorone/baton-okta.
Quick Check
Is the function name 'parsePageToken' exported or unexported?
Exercise: Spot the Visibility
The starter code has four visibility bugs. Fix them: the client struct should be exported, the field HttpClient should not be exported, the function new should be exported, and the function BuildURL should not be exported.
Gopher says
Packages keep Go projects organized and maintainable. Capitalization as visibility is unusual at first, but you’ll love how readable it makes code - you always know what’s public at a glance.
In baton-junction, scanning any file tells you instantly what’s meant for
other packages (Client, New, Connector) versus internal implementation
details (httpClient, baseURL, doGet).
Next: context!
Next Lesson
Context

