This document outlines the migration of the MAGE (Magic Another Game Engine) server from Java to Go while maintaining API compatibility.
Mage.Server/src/main/java/mage/server/Main.java (582 lines)MageServerImpl.java (1,356 lines, 60+ RPC methods)Critical Decision Point: Choose replacement for JBoss Remoting
Option A - gRPC (Recommended)
.proto files for all 60+ RPC methodsOption B - Custom TCP + Protocol Buffers
Option C - Hybrid: gRPC + WebSocket
Action Items:
Replace: H2 + SQLite + ORMLite With: PostgreSQL + pgx/GORM OR keep SQLite + sqlx
Schema Migration:
Tables to Migrate:
card_info, expansion_info, token_info (cards.h2.db)authorized_user (users.h2.db)user_stats (stats.sqlite)table_records (table_records.sqlite)Repository Pattern:
Replace: XML config + JAXB With: Viper (YAML/JSON) or Go structs with env var support
Config Structure (port from config/config.xml):
type ServerConfig struct {
ServerAddress string
ServerName string
Port int
SecondaryBindPort int
BacklogSize int
NumAcceptThreads int
MaxPoolSize int
LeasePeriod int
MaxGameThreads int
MaxSecondsIdle int
UserNameValidation UserNameConfig
PasswordValidation PasswordConfig
AuthSettings AuthConfig
MailSettings MailConfig
PluginPaths []string
}Action Items:
mage-server-go/
├── cmd/
│ └── server/
│ └── main.go # Entry point
├── internal/
│ ├── server/
│ │ └── server.go # Core server (MageServerImpl port)
│ ├── session/
│ │ ├── session.go # Session management
│ │ └── manager.go # SessionManager
│ ├── user/
│ │ ├── user.go # User domain model
│ │ ├── manager.go # UserManager
│ │ └── repository.go # User persistence
│ ├── table/
│ │ ├── controller.go # TableController port
│ │ └── manager.go # TableManager
│ ├── game/
│ │ ├── controller.go # GameController port
│ │ ├── session.go # Player/watcher sessions
│ │ ├── manager.go # GameManager
│ │ ├── replay.go # Replay system
│ │ └── worker.go # Game execution
│ ├── tournament/
│ │ ├── controller.go # TournamentController
│ │ ├── session.go # Tournament sessions
│ │ └── manager.go # TournamentManager
│ ├── draft/
│ │ ├── controller.go # DraftController
│ │ ├── session.go # Draft sessions
│ │ └── manager.go # DraftManager
│ ├── chat/
│ │ ├── chat.go # Chat session
│ │ └── manager.go # ChatManager
│ ├── repository/
│ │ ├── cards.go # CardRepository
│ │ ├── users.go # AuthorizedUserRepository
│ │ ├── stats.go # UserStatsRepository
│ │ └── records.go # TableRecordRepository
│ ├── rating/
│ │ └── glicko.go # GlickoRatingSystem
│ ├── mail/
│ │ ├── client.go # MailClient interface
│ │ ├── smtp.go # SMTP implementation
│ │ └── mailgun.go # Mailgun implementation
│ ├── auth/
│ │ └── auth.go # Authentication/password hashing
│ ├── room/
│ │ ├── room.go # Room abstraction
│ │ └── games_room.go # GamesRoom (main lobby)
│ └── config/
│ └── config.go # Configuration loader
├── pkg/
│ ├── proto/ # Generated protobuf (if using gRPC)
│ │ └── mage/
│ │ └── v1/
│ └── models/ # Shared data models
│ ├── views.go # Client view models
│ └── enums.go # Enums (ClientCallbackMethod, etc.)
├── api/
│ └── proto/ # .proto definitions
│ └── mage/
│ └── v1/
│ ├── server.proto # Main RPC service
│ ├── game.proto # Game-related messages
│ └── tournament.proto
├── migrations/ # Database migrations
│ ├── 001_create_users.sql
│ ├── 002_create_cards.sql
│ └── ...
├── config/
│ └── config.yaml # Default configuration
├── plugins/ # Plugin system (TBD approach)
└── go.mod
Port: Session.java (554 lines) → internal/session/session.go
Key Features:
Implementation:
type Session struct {
SessionID string
UserID string
Host string
IsAdmin bool
Connected bool
LastActivity time.Time
LeasePeriod time.Duration
CallbackChan chan ClientCallback
mu sync.RWMutex
}
type SessionManager interface {
CreateSession(sessionID string, host string) *Session
GetSession(sessionID string) (*Session, bool)
DisconnectSession(sessionID string, reason DisconnectReason)
ValidateSession(sessionID string) bool
CleanupExpiredSessions()
}Action Items:
Port: UserManagerImpl.java → internal/user/manager.go
Responsibilities:
Action Items:
Port: AuthorizedUserRepository.java → internal/auth/auth.go
Current: SHA-256 with 1024 iterations (Apache Shiro) Recommended: bcrypt or Argon2id (Go standard)
Features:
Action Items:
Port: 60+ methods from MageServerImpl.java (1,356 lines)
Method Categories:
Authentication (6 methods):
ConnectUser - User login/session creationConnectAdmin - Admin authenticationConnectSetUserData - User metadata updateAuthRegister - New user registrationAuthSendTokenToEmail - Password reset tokenAuthResetPassword - Password reset with tokenRoom/Lobby (5 methods):
ServerGetMainRoomId - Get main lobby IDRoomGetUsers - List users in roomRoomGetFinishedMatches - Recent matchesRoomGetAllTables - List all game tablesRoomGetTableById - Get specific tableTable Management (10 methods):
RoomCreateTable - Create game tableRoomCreateTournament - Create tournamentRoomJoinTable - Join gameRoomJoinTournament - Join tournamentRoomLeaveTableOrTournament - Leave tableRoomWatchTable - Watch gameRoomWatchTournament - Watch tournamentTableSwapSeats - Swap player seatsTableRemove - Remove table (admin)TableIsOwner - Check table ownershipGame Execution (15 methods):
GameJoin - Join game as playerGameWatchStart / GameWatchStop - SpectatingGameGetView - Get game state viewSendPlayerUUID / SendPlayerString / SendPlayerBoolean / SendPlayerInteger / SendPlayerManaType - Player inputsSendPlayerAction - Generic player actionMatchStart - Start matchMatchQuit - Quit matchDraft (6 methods):
DraftJoin - Join draftSendDraftCardPick - Pick cardSendDraftCardMark - Mark cardDraftSetBoosterLoaded - Confirm booster loadDraftQuit - Leave draftTournament (4 methods):
TournamentJoin - Join tournamentTournamentStart - Start tournamentTournamentQuit - Leave tournamentTournamentFindById - Get tournament infoChat (7 methods):
ChatJoin - Join chatChatLeave - Leave chatChatSendMessage - Send messageChatFindByTable / ChatFindByGame / ChatFindByTournament / ChatFindByRoom - Get chat IDReplay (6 methods):
ReplayInit / ReplayStart / ReplayStopReplayNext / ReplayPrevious / ReplaySkipForwardAdmin (9 methods):
AdminGetUsers - List all usersAdminDisconnectUser - Force disconnectAdminMuteUser - Mute userAdminLockUser - Lock accountAdminActivateUser / AdminToggleActivateUser - Account activationAdminEndUserSession - End sessionAdminTableRemove - Remove tableAdminSendBroadcastMessage - Server broadcastServer Info (4 methods):
GetServerState - Server config/game typesPing - Keep-aliveServerGetPromotionMessages - MOTDServerAddFeedbackMessage - User feedbackDeck Management (2 methods):
DeckSubmit - Submit deck for gameDeckSave - Save deckImplementation Strategy:
type MageServer interface {
// Auth
ConnectUser(ctx context.Context, req *ConnectUserRequest) (*ConnectUserResponse, error)
// ... all 60+ methods
}
type mageServerImpl struct {
sessionMgr session.Manager
userMgr user.Manager
tableMgr table.Manager
gameMgr game.Manager
tournamentMgr tournament.Manager
draftMgr draft.Manager
chatMgr chat.Manager
config *config.ServerConfig
}Port: TableController.java (1,265 lines) → internal/table/controller.go
Responsibilities:
Action Items:
Port: GameController.java (1,662 lines) → internal/game/controller.go
Critical Component: Interfaces with game engine (separate from server)
Responsibilities:
Action Items:
Port: TournamentController.java → internal/tournament/controller.go
Types: Swiss, Elimination, Booster Draft, Sealed, etc.
Action Items:
Port: DraftController.java → internal/draft/controller.go
Types: Booster draft, cube draft, Winston draft
Action Items:
Port: Callback system from Session.fireCallback()
Current: InvokerCallbackHandler with ClientCallback messages
Callback Types (from ClientCallbackMethod enum):
Implementation Options:
Option A - gRPC Streaming:
service MageServer {
rpc Subscribe(SubscribeRequest) returns (stream ServerEvent);
}Option B - WebSocket:
func (s *Session) StartCallbackHandler() {
conn := s.websocketConn
for callback := range s.CallbackChan {
conn.WriteJSON(callback)
}
}Action Items:
Port: ChatManagerImpl.java → internal/chat/manager.go
Features:
Action Items:
Port: CardRepository.java → internal/repository/cards.go
Data: ~40,000 Magic cards with expansion info
Action Items:
Port: UserStatsRepository.java (379 lines) → internal/repository/stats.go
Features:
Action Items:
internal/rating/glicko.go)Port: TableRecordRepository.java → internal/repository/records.go
Current: Protocol Buffers serialization (can keep!)
Action Items:
.proto file (result.proto)protoc --go_out=. result.protoCurrent: Dynamic JAR loading with custom ClassLoader
Problem: Go doesn’t support dynamic loading like Java
Options:
Option A - Go Plugins (Standard Library):
plugin package (Linux/macOS only, not Windows).so filesOption B - Pre-compiled Registry:
Option C - gRPC Plugin System (HashiCorp go-plugin):
Recommendation: Option B (pre-compiled) for initial migration, consider Option C if extensibility is critical
Action Items:
Port: MailClientImpl.java + MailgunClientImpl.java → internal/mail/
Current: SMTP (JavaMail) + Mailgun API (Jersey client)
Go Libraries:
net/smtp or gomailmailgun-go SDKAction Items:
Port: FeedbackServiceImpl.java → simple endpoint
Action Items:
Replace: Log4j With: Zap (structured, high-performance) or Logrus
Action Items:
Action Items:
Action Items:
Action Items:
Action Items:
Action Items:
Action Items:
google.golang.org/grpc - If using gRPCgoogle.golang.org/protobuf - Already in usegithub.com/jackc/pgx/v5 - PostgreSQL (recommended)modernc.org/sqlite - Keep SQLitegorm.io/gorm - If you want ORM stylegithub.com/spf13/viper - Configuration managementgo.uber.org/zap - Structured logginggolang.org/x/crypto/bcrypt - Password hashinggithub.com/mailgun/mailgun-go/v4 - Mailgungopkg.in/gomail.v2 - SMTPgithub.com/go-playground/validator/v10 - Input validationgithub.com/stretchr/testify - Test assertionsgithub.com/prometheus/client_golang - Prometheusgithub.com/gorilla/websocket or nhooyr.io/websocketnet package (standard library)Critical: Existing Java clients must continue to work
Approach:
Recommendation: Unless perfect wire compatibility is required, consider releasing a new client version that uses gRPC/WebSocket.
Total Estimate: 25-34 weeks (6-8 months) for a team of 2-3 developers