Architecture Patterns
Overview
Section titled “Overview”Proven patterns for organising your Wails application.
Service Layer Pattern
Section titled “Service Layer Pattern”Structure
Section titled “Structure”app/├── main.go├── services/│ ├── user_service.go│ ├── data_service.go│ └── file_service.go└── models/ └── user.goImplementation
Section titled “Implementation”// Service interfacetype UserService interface { Create(email, password string) (*User, error) GetByID(id int) (*User, error) Update(user *User) error Delete(id int) error}
// Implementationtype 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
Section titled “Repository Pattern”Structure
Section titled “Structure”// Repository interfacetype UserRepository interface { Create(user *User) error FindByID(id int) (*User, error) Update(user *User) error Delete(id int) error}
// Service uses repositorytype 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
Section titled “Event-Driven Architecture”Event Bus
Section titled “Event Bus”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) }}// SubscribeeventBus.Subscribe("user.created", func(data interface{}) { user := data.(*User) sendWelcomeEmail(user)})
// PublisheventBus.Publish("user.created", user)Dependency Injection
Section titled “Dependency Injection”Manual DI
Section titled “Manual DI”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
Section titled “Using Wire”//go:build wireinject
func InitializeApp() (*App, error) { wire.Build( openDatabase, NewUserService, NewFileService, NewApp, ) return nil, nil}State Management
Section titled “State Management”Centralised State
Section titled “Centralised State”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
Section titled “Best Practices”- Separate concerns
- Use interfaces
- Inject dependencies
- Handle errors properly
- Keep services focused
- Document architecture
❌ Don’t
Section titled “❌ 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
Section titled “Next Steps”- Security - Security best practices
- Best Practices - Bindings best practices