package.json + node_modules/ → go.mod + глобальный кэш в $GOPATH/pkg/mod
import { thing } from './module' → import "myproject/pkg/logger" (абсолютные пути от модуля)
index.js → пакет = директория, имя пакета в package xxx
private в TS → строчная буква (не экспортируется)
public → заглавная буква (экспортируется)
node_modules/.bin → $GOPATH/bin
go.mod, go.sum, семантическое версионирование
package name, один пакет на директориюinternal/ — пакеты, доступные только внутри модуляpkg/ — публичные библиотеки (идиоматическое соглашение)cmd/ — точки входа (исполняемые файлы)init()go get, go mod tidy, go mod vendormkdir go-packages && cd go-packages
go mod init github.com/yourname/go-packages
# Создаём стандартную структуру проекта
mkdir -p cmd/server
mkdir -p internal/user
mkdir -p internal/database
mkdir -p pkg/logger
mkdir -p pkg/middleware
mkdir -p api
go-packages/ │ ├── go.mod # Модуль: имя, версия Go, зависимости ├── go.sum # Контрольные суммы зависимостей (как lock-файл) │ ├── cmd/ # Точки входа (исполняемые файлы) │ └── server/ │ └── main.go # package main → бинарник │ ├── internal/ # Приватные пакеты (только для ЭТОГО модуля) │ ├── user/ # package user │ │ ├── user.go # Определение структуры User │ │ └── repository.go # Работа с хранилищем пользователей │ └── database/ │ └── postgres.go # Подключение к БД │ ├── pkg/ # Публичные библиотеки (могут импортироваться извне) │ ├── logger/ │ │ └── logger.go # package logger │ └── middleware/ │ └── middleware.go # HTTP middleware │ └── api/ # API-контракты (OpenAPI, proto) └── openapi.yaml
module github.com/yourname/go-packages
go 1.22
require (
github.com/google/uuid v1.6.0
)
require (
// indirect-зависимости (транзитивные)
)
// Пакет user — внутренний пакет модуля.
// Доступен ТОЛЬКО из go-packages и его подпакетов.
// Внешние модули НЕ могут импортировать internal/.
package user
import (
"errors"
"fmt"
"strings"
)
// User — ЭКСПОРТИРУЕМАЯ структура (заглавная буква U).
// Доступна везде, где импортирован пакет user.
type User struct {
ID int // Экспортируемое поле
Name string // Экспортируемое поле
Email string // Экспортируемое поле
// password — НЕэкспортируемое поле (строчная буква).
// Доступно только внутри пакета user.
password string
}
// NewUser — экспортируемая функция-конструктор.
// Возвращает *User. Пароль хешируется внутри.
func NewUser(id int, name, email, password string) (*User, error) {
// Валидация (пакетная ответственность)
if strings.TrimSpace(name) == "" {
return nil, errors.New("name must not be empty")
}
if !strings.Contains(email, "@") {
return nil, errors.New("invalid email format")
}
if len(password) < 8 {
return nil, errors.New("password too short (min 8 chars)")
}
return &User{
ID: id,
Name: name,
Email: email,
password: hashPassword(password), // приватная функция пакета
}, nil
}
// ValidatePassword — экспортируемый метод для проверки пароля
func (u *User) ValidatePassword(pass string) bool {
return hashPassword(pass) == u.password
}
// Info — экспортируемый метод (возвращает публичную информацию)
func (u *User) Info() string {
return fmt.Sprintf("User{ID:%d, Name:%q, Email:%q}", u.ID, u.Name, u.Email)
}
// hashPassword — НЕэкспортируемая функция (строчная буква).
// Видна только внутри пакета user.
func hashPassword(pass string) string {
// В реальности — bcrypt/argon2id.
// Здесь простой пример (НЕ ДЕЛАЙ ТАК В ПРОДАКШЕНЕ!)
return fmt.Sprintf("hashed_%s", pass)
}
// sanitize — неэкспортируемая (внутренняя утилита)
func sanitize(s string) string {
return strings.TrimSpace(s)
}
package user // Тот же пакет user — можно в разных файлах!
import (
"fmt"
"sync"
)
// UserRepository — ЭКСПОРТИРУЕМЫЙ интерфейс.
// Описывает контракт хранилища пользователей.
type UserRepository interface {
Save(u *User) error
FindByID(id int) (*User, error)
FindAll() []*User
}
// InMemoryRepo — НЕэкспортируемая структура (строчная).
// Реализует UserRepository в памяти.
type InMemoryRepo struct {
mu sync.RWMutex
users map[int]*User
}
// NewInMemoryRepo — экспортируемый конструктор для InMemoryRepo
func NewInMemoryRepo() *InMemoryRepo {
return &InMemoryRepo{
users: make(map[int]*User),
}
}
// Save — реализация интерфейса UserRepository
func (r *InMemoryRepo) Save(u *User) error {
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.users[u.ID]; exists {
return fmt.Errorf("user with ID %d already exists", u.ID)
}
r.users[u.ID] = u
return nil
}
// FindByID — поиск по ID
func (r *InMemoryRepo) FindByID(id int) (*User, error) {
r.mu.RLock()
defer r.mu.RUnlock()
u, ok := r.users[id]
if !ok {
return nil, fmt.Errorf("user %d not found", id)
}
return u, nil
}
// FindAll — возвращает всех пользователей
func (r *InMemoryRepo) FindAll() []*User {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]*User, 0, len(r.users))
for _, u := range r.users {
result = append(result, u)
}
return result
}
package database
import (
"context"
"fmt"
"time"
)
// Config — конфигурация подключения к БД
type Config struct {
Host string
Port int
User string
Password string
DBName string
SSLMode string
}
// DSN — формирует строку подключения (Data Source Name)
func (c Config) DSN() string {
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
c.Host, c.Port, c.User, c.Password, c.DBName, c.SSLMode,
)
}
// Connect — имитация подключения к PostgreSQL
func Connect(ctx context.Context, cfg Config) error {
// В реальном коде здесь был бы pgxpool.New(ctx, cfg.DSN())
fmt.Printf("Connecting to PostgreSQL at %s:%d...\n", cfg.Host, cfg.Port)
// Имитация таймаута подключения
select {
case <-time.After(100 * time.Millisecond):
fmt.Println("Connected successfully!")
return nil
case <-ctx.Done():
return fmt.Errorf("connection cancelled: %w", ctx.Err())
}
}
// Пакет logger — ПУБЛИЧНАЯ библиотека.
// Может быть импортирована другими модулями.
package logger
import (
"fmt"
"io"
"os"
"time"
)
// Level — тип уровня логирования
type Level int
// Константы уровней. iota — автоинкремент.
const (
Debug Level = iota // 0
Info // 1
Warn // 2
Error // 3
)
// String — реализация интерфейса Stringer
func (l Level) String() string {
switch l {
case Debug: return "DEBUG"
case Info: return "INFO"
case Warn: return "WARN"
case Error: return "ERROR"
default: return "UNKNOWN"
}
}
// Logger — структура логгера
type Logger struct {
level Level
output io.Writer // Куда пишем (stdout, файл, etc.)
}
// New — конструктор. Принимает уровень и опционально Writer.
func New(level Level, outputs ...io.Writer) *Logger {
out := io.Discard // По умолчанию никуда не пишем
if len(outputs) > 0 {
out = outputs[0]
} else {
out = os.Stdout // По умолчанию stdout
}
return &Logger{level: level, output: out}
}
// log — НЕэкспортируемый метод (внутренний)
func (l *Logger) log(level Level, format string, args ...any) {
if level >= l.level {
timestamp := time.Now().Format(time.RFC3339)
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(l.output, "[%s] %s %s\n", timestamp, level, msg)
}
}
// Debug — публичный метод
func (l *Logger) Debug(format string, args ...any) {
l.log(Debug, format, args...)
}
// Info — публичный метод
func (l *Logger) Info(format string, args ...any) {
l.log(Info, format, args...)
}
// Warn — публичный метод
func (l *Logger) Warn(format string, args ...any) {
l.log(Warn, format, args...)
}
// Error — публичный метод
func (l *Logger) Error(format string, args ...any) {
l.log(Error, format, args...)
}
package middleware
import (
"fmt"
"time"
)
// Timer — middleware для замера времени выполнения
func Timer(name string) func() {
start := time.Now()
return func() {
elapsed := time.Since(start)
fmt.Printf("[%s] completed in %v\n", name, elapsed)
}
}
// package main — точка входа. Только main компилируется в бинарник.
package main
import (
"context"
"fmt"
"os"
"time"
// Абсолютные пути импорта от корня модуля
"github.com/yourname/go-packages/internal/database"
"github.com/yourname/go-packages/internal/user"
"github.com/yourname/go-packages/pkg/logger"
"github.com/yourname/go-packages/pkg/middleware"
)
func main() {
// === 1. Инициализация логгера ===
log := logger.New(logger.Info)
log.Info("Server starting...")
// === 2. Подключение к БД ===
dbCfg := database.Config{
Host: "localhost", Port: 5432,
User: "postgres", Password: "secret",
DBName: "mydb", SSLMode: "disable",
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := database.Connect(ctx, dbCfg); err != nil {
log.Error("Database connection failed: %v", err)
os.Exit(1)
}
// === 3. Создание пользователей ===
repo := user.NewInMemoryRepo()
u1, err := user.NewUser(1, "Alice", "alice@example.com", "securepass123")
if err != nil {
log.Error("Failed to create user Alice: %v", err)
} else {
repo.Save(u1)
log.Info("Created: %s", u1.Info())
}
u2, err := user.NewUser(2, "Bob", "bob@example.com", "anotherpass456")
if err != nil {
log.Error("Failed to create user Bob: %v", err)
} else {
repo.Save(u2)
log.Info("Created: %s", u2.Info())
}
// === 4. Демонстрация валидации (ошибка) ===
_, err = user.NewUser(3, "", "bad-email", "short")
if err != nil {
log.Warn("Validation error (expected): %v", err)
}
// === 5. Middleware: замер времени ===
defer middleware.Timer("main")()
// === 6. Вывод всех пользователей ===
fmt.Println("\n=== All Users ===")
for _, u := range repo.FindAll() {
fmt.Printf(" %s\n", u.Info())
fmt.Printf(" Password valid? %v\n", u.ValidatePassword("wrongpass"))
fmt.Printf(" Password valid? %v\n", u.ValidatePassword("securepass123"))
}
log.Info("Server stopped gracefully")
}
# Скачиваем зависимости (uuid)
go get github.com/google/uuid
# Очищаем неиспользуемые зависимости
go mod tidy
# Запуск
go run ./cmd/server/main.go
# ИЛИ (если пакет main один в cmd/server)
go run ./cmd/server
# Сборка бинарника
go build -o server ./cmd/server
./server
# Просмотр зависимостей
go list -m all
# Просмотр дерева зависимостей
go mod graph
# Вендоринг (копирование зависимостей в vendor/)
go mod vendor
| Уровень | Синтаксис | Видимость |
|---|---|---|
| Экспортируемый | User, NewUser, ValidatePassword | Везде, где импортирован пакет |
| Неэкспортируемый | password, hashPassword | Только внутри своего пакета |
internal/ | Специальная директория | Только внутри текущего модуля |
package xxx.
internal/ не могут импортироваться извне модуля. Компилятор блокирует.cmd/xxx — отдельный бинарник (package main).💡 Best practices от сеньоров:
pkg/logger → package logger. Не package log (конфликт со стандартной).💡 Для Node.js разработчика:
index.js не нужен — Go видит все .go-файлы в пакете автоматически.internal/ — аналог "exports" в package.json с ограничением доступа.../../ в импортах — только абсолютные пути от корня модуля.require — только import на уровне файла.