In Go, you can control the visibility of a struct or function using capitalization:
- If a name starts with an uppercase letter, it is exported (public).
 - If it starts with a lowercase letter, it is unexported (private to the package).
 
Let’s consider the following project structure:
.
├── go.mod
├── main.go
├── repository
│   └── user_repo.go
└── service
    └── user_service.go
Suppose we have a userRepo in the repository package that we want to use inside userService. However:
- We don’t want users of 
userServiceto be able to create instances ofuserRepo. - We don’t want 
userServicemethods to access the database directly. 
To solve this, we can keep the userRepo struct private and expose it only through a constructor function:
// repository/user_repo.go
package repository
type userRepo struct {
    db *driver.Database
}
func NewUserRepo() userRepo {
    dbInstance := /* initialize your DB here */
    return userRepo{
        db: dbInstance,
    }
}
func (ur userRepo) Get(ctx context.Context, userId string) (*User, error) {
    // Implementation here...
}
This works great as long as you don’t need to refer to its type outside the package:
func example() {
    repo := repository.NewUserRepo()
    user, err := repo.Get(context.Background(), "123")
    // ...
}
The Problem
Now suppose you’re building a userService that stores a userRepo as a field:
// service/user_service.go
package service
type userService struct {
    userRepo repository.userRepo // ❌ This won't compile — userRepo is private!
}
func NewUserService() userService {
    return userService{
        userRepo: repository.NewUserRepo(), // ❌ This also won't compile
    }
}
You can’t use the unexported userRepo type outside of the repository package—even if you’re accessing it correctly via a constructor.
The Solution: Use Interfaces
The idiomatic Go solution is to define an interface that exposes only the behavior you need. This way:
- You keep the implementation (
userRepo) private. - You allow other packages to depend on the interface (
UserRepo), not the implementation. 
repository/user_repo.go
package repository
type UserRepo interface {
    Get(ctx context.Context, userId string) (*User, error)
}
type userRepo struct {
    db *driver.Database
}
func NewUserRepo() UserRepo {
    dbInstance := /* initialize your DB here */
    return userRepo{
        db: dbInstance,
    }
}
func (ur userRepo) Get(ctx context.Context, userId string) (*User, error) {
    // Implementation...
}
service/user_service.go
package service
import "your_project/repository"
type userService struct {
    userRepo repository.UserRepo
}
func NewUserService() userService {
    return userService{
        userRepo: repository.NewUserRepo(),
    }
}
func (svc userService) DoSomething(ctx context.Context, userId string) (*SomeOutput, error) {
    user, err := svc.userRepo.Get(ctx, userId)
    if err != nil {
        return nil, err
    }
    // Perform additional logic...
    return &SomeOutput{/*...*/}, nil
}
Conclusion
By using interfaces in Go, you can:
- Keep your internal logic and implementation details private.
 - Expose only the behavior that other packages need.
 - Decouple modules.
 - Adhere to clean, idiomatic Go design.