Skip to content

Architecture Patterns

Proven patterns for organising your Wails application.

app/
├── main.go
├── services/
│ ├── user_service.go
│ ├── data_service.go
│ └── file_service.go
└── models/
└── user.go
// Service interface
type UserService interface {
Create(email, password string) (*User, error)
GetByID(id int) (*User, error)
Update(user *User) error
Delete(id int) error
}
// Implementation
type userService struct {
app *application.Application
db *sql.DB
}
func NewUserService(app *application.Application, db *sql.DB) UserService {
return &userService{app: app, db: db}
}
// Repository interface
type UserRepository interface {
Create(user *User) error
FindByID(id int) (*User, error)
Update(user *User) error
Delete(id int) error
}
// Service uses repository
type UserService struct {
repo UserRepository
}
func (s *UserService) Create(email, password string) (*User, error) {
user := &User{Email: email}
return user, s.repo.Create(user)
}
type EventBus struct {
app *application.Application
listeners map[string][]func(interface{})
mu sync.RWMutex
}
func (eb *EventBus) Subscribe(event string, handler func(interface{})) {
eb.mu.Lock()
defer eb.mu.Unlock()
eb.listeners[event] = append(eb.listeners[event], handler)
}
func (eb *EventBus) Publish(event string, data interface{}) {
eb.mu.RLock()
handlers := eb.listeners[event]
eb.mu.RUnlock()
for _, handler := range handlers {
go handler(data)
}
}
// Subscribe
eventBus.Subscribe("user.created", func(data interface{}) {
user := data.(*User)
sendWelcomeEmail(user)
})
// Publish
eventBus.Publish("user.created", user)
type App struct {
userService *UserService
fileService *FileService
db *sql.DB
}
func NewApp() *App {
db := openDatabase()
return &App{
db: db,
userService: NewUserService(db),
fileService: NewFileService(db),
}
}
wire.go
//go:build wireinject
func InitializeApp() (*App, error) {
wire.Build(
openDatabase,
NewUserService,
NewFileService,
NewApp,
)
return nil, nil
}
type AppState struct {
currentUser *User
settings *Settings
mu sync.RWMutex
}
func (s *AppState) SetUser(user *User) {
s.mu.Lock()
defer s.mu.Unlock()
s.currentUser = user
}
func (s *AppState) GetUser() *User {
s.mu.RLock()
defer s.mu.RUnlock()
return s.currentUser
}
  • Separate concerns
  • Use interfaces
  • Inject dependencies
  • Handle errors properly
  • Keep services focused
  • Document architecture
  • Don’t create god objects
  • Don’t tightly couple components
  • Don’t skip error handling
  • Don’t ignore concurrency
  • Don’t over-engineer