gui/docs/ref/wails-v3/guides/architecture.mdx
Snider 4bdbb68f46
Some checks failed
Security Scan / security (push) Failing after 9s
Test / test (push) Failing after 1m21s
refactor: update import path from go-config to core/config
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-14 10:26:36 +00:00

199 lines
3.4 KiB
Text

---
title: Architecture Patterns
description: Design patterns for Wails applications
sidebar:
order: 7
---
## Overview
Proven patterns for organising your Wails application.
## Service Layer Pattern
### Structure
```
app/
├── main.go
├── services/
│ ├── user_service.go
│ ├── data_service.go
│ └── file_service.go
└── models/
└── user.go
```
### Implementation
```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 Pattern
### Structure
```go
// 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)
}
```
## Event-Driven Architecture
### Event Bus
```go
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)
}
}
```
### Usage
```go
// Subscribe
eventBus.Subscribe("user.created", func(data interface{}) {
user := data.(*User)
sendWelcomeEmail(user)
})
// Publish
eventBus.Publish("user.created", user)
```
## Dependency Injection
### Manual DI
```go
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),
}
}
```
### Using Wire
```go
// wire.go
//go:build wireinject
func InitializeApp() (*App, error) {
wire.Build(
openDatabase,
NewUserService,
NewFileService,
NewApp,
)
return nil, nil
}
```
## State Management
### Centralised State
```go
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
}
```
## Best Practices
### ✅ Do
- Separate concerns
- Use interfaces
- Inject dependencies
- Handle errors properly
- Keep services focused
- Document architecture
### ❌ Don't
- Don't create god objects
- Don't tightly couple components
- Don't skip error handling
- Don't ignore concurrency
- Don't over-engineer
## Next Steps
- [Security](/guides/security) - Security best practices
- [Best Practices](/features/bindings/best-practices) - Bindings best practices