Level 6 /

Packages & Visibility

Even a small town needs zoning. You cannot put the switchyard in the middle of the apple orchard. At the Baton Junction town planning office, Gopher reviews zoning maps and construction permits, organizing the project into districts with clear boundaries and clear rules.

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:

entry pointlibrary packages
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).

keywordUppercase = publiclowercase = 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, NewuserBuilder, groupBuilder
Client, User, RoledoGet, doPut, doDelete
GetUsers, CreateUserparsePageToken, mapUserStatus
ConfigdefaultPageSize, 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:

keywordGroup 1: Standard libraryGroup 2: Internal packagesGroup 3: External dependencies
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"
)
GroupContentsHow to Recognize
1Standard libraryNo dots: "fmt", "context"
2Your packagesStarts with module path: "example/baton-junction/..."
3External depsStarts 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.

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!

Explore the related connector code files

Next Lesson

Context

Continue