Maintena Sync: a Computerized Maintenance Management System
Completed

Maintena Sync: a Computerized Maintenance Management System

July 2025
4 Technologies
2 months
Team Project

Building Maintena Sync: Enterprise Maintenance Management System

Tech Stack

C++ Qt6 SQL Cross-platform

Key Features

  • Asset Management
  • Preventive Maintenance
  • Document Management
  • Compliance Tracking
  • Job Scheduling
  • Role-based Access

Challenges

  • Database Design
  • Cross-platform Deployment
  • Full-stack Development
  • Performance Optimization

Building Maintena Sync: Enterprise Maintenance Management System

A comprehensive desktop application serving commercial maritime operations with real-time synchronization, offline capabilities, and regulatory compliance features.

Maintena Sync represents the evolution from inefficient paper-based processes to a comprehensive digital solution that’s transforming how maritime companies manage their maintenance operations. What began as field research and business process analysis evolved into a commercial desktop application now serving customers across multiple maritime operations.

Image of survey in engine control room

The Problem: Reactive Maintenance Costs

Traditional maintenance approaches suffer from several critical issues:

  • Reactive repairs instead of preventive maintenance
  • Paper-based tracking leading to lost records and missed schedules
  • Costly downtime due to unexpected equipment failures
  • Compliance headaches with expiring certificates and licenses
  • Inventory chaos without integrated procurement tracking

The maritime industry loses millions annually due to these systemic inefficiencies. A single day of unplanned vessel downtime can cost operators anywhere from hundreds to thousands of dollars, making efficient maintenance management a critical competitive advantage.

Technical Stacks

Core Technology Decisions

C++ with Qt6 Framework

  • Chose C++ for performance-critical operations and complex business logic
  • Qt6 provided cross-platform GUI capabilities with native look and feel
  • Modular architecture allowing independent feature development

Database Design

  • SQLite (encrypted) for local data and MySQL for cloud data
  • Complex relational model handling asset hierarchies and maintenance relationships
  • Optimized queries for real-time reporting and large dataset operations

Security Implementation

  • Role-based access control with granular permissions
  • Encrypted data storage for sensitive operational information

Maintena UI preview

System Architecture

Desktop Level

At desktop level architecture, Qt generally use Model/View programming to manage relationship between data and the way it’s presented to the user See Qt Doc on Model/View. In the implementation, there are several extra steps, for example, how the data is served into the model (Data Service). And on each layer, there are various design patterns that we use, such as Strategy Pattern, Command Pattern, Bridge Pattern, Thread Manager (Consumer-Worker) and Singleton to serve the data into the model. On the model and view itself, in Qt, there are lots of class provided altough there are base class, such as QAbstractItemModel class, that is inherited by class that we usually use such as QStringListModel, QStandardItemModel, QFileSystemModel, etc that will be displayed in various views, such as QTableView or QListView, by Qt.

Model view programming

System Level (Ecosystem)

Client Layer: The diagram shows a desktop client environment where users interact with the Maintena application, which maintains a Local DB (SQLite Cipher) for offline operations. This client-side database enables full functionality without network connectivity.

Ecosystem diagram for the app

Communication Layer: The desktop client communicates with the server infrastructure through two primary channels: HTTP/HTTPS API calls to the Server API for standard data operations and synchronization requests WebSocket connections for real-time bidirectional communication, enabling instant notifications and online presence detection

Server Infrastructure: The Server API (GoLang-based) acts as the central orchestrator, managing all backend services and data flow. It interfaces with multiple specialized database instances:

Data Storage Layer:

  • Cloud Maintena DB: Primary MySQL database containing all fleet and vessel operational data
  • Auth DB: Dedicated authentication database managing user credentials, permissions, and session tokens
  • Backup DB: Redundant storage ensuring data recovery capabilities Object Storage: AWS S3 integration for file management (documents, images, manuals)

Service Layer: A dedicated Notification Service handles multi-channel alert delivery, sending notifications through email and other messaging platforms directly to users, independent of the main application flow.

Data Flow: The architecture demonstrates bidirectional synchronization between local and cloud databases, with the Server API coordinating all data transactions, authentication requests, and real-time communication while maintaining separation of concerns across specialized data stores. This represents a distributed, resilient system designed for maritime operations where connectivity is intermittent but data availability is critical.

Design System

We can write deep dive into several reasons as to why we choose Fluent UI design system, but in general, here’s why the design system is excellent for a CMMS desktop software.

Screenshoot of Maintena Login Page

  1. Enterprise Software Standards
    Fluent UI is Microsoft’s enterprise design language, making it immediately familiar to business users who work with Office 365, Teams, and other professional tools. Maritime professionals often use Microsoft products in their shore-based operations, so this familiarity reduces learning curves and increases user adoption.

  2. Cross-Platform Consistency
    Since Maintena runs on Windows, macOS, and Linux (via Qt), Fluent UI provides design principles that work across all platforms while maintaining native feel. The clean, modern aesthetic shown in the login screen translates well across different operating systems.

  3. Professional Maritime Context
    The design system’s emphasis on: Clarity and Focus: Critical for maritime operations where mistakes have serious consequences Information Hierarchy: Essential for complex maintenance data and compliance requirements Accessible Color Schemes: Important for various lighting conditions on vessels Consistent Iconography: Reduces cognitive load for crew members with varying technical backgrounds

  4. Technical Implementation Benefits
    • Colors: The blue background (#0078d4) follows Fluent’s primary color palette
    • Typography: Clean, readable fonts suitable for technical documentation
    • Spacing: Consistent margins and padding throughout the interface
    • Elevation: Subtle shadows and layering (visible in yoth login modal)
  5. Maritime-Specific Advantages
    Visual Stability: Fluent’s emphasis on calm, stable interfaces aligns with maritime needs where operators work in potentially stressful, moving environments.
    Data Density: The design system handles complex information displays well - crucial for maintenance schedules, equipment hierarchies, and compliance tracking.
    Touch-Friendly: Though desktop-first, Fluent principles work for future tablet implementations for bridge or engine room use.

  6. User Experience Rationale
    Professional Credibility: The polished, enterprise-grade appearance builds trust with maritime companies investing in critical maintenance software.
    Reduced Training Time: Familiar interaction patterns mean crew members can focus on maintenance tasks rather than learning interface quirks.
    Scalability: As the feature set grows (maintenance scheduling, document management, reporting), Fluent UI provides consistent patterns for complex workflows.

  7. Brand Alignment
    The maritime blue color scheme naturally aligns with Fluent’s blue accents while reinforcing the nautical theme. The clean, technical aesthetic suggests reliability and precision - key values for maintenance software. Bottom Line: Fluent UI gives Maintena Sync the professional, trustworthy appearance that maritime enterprises expect while providing a familiar, efficient user experience that reduces training overhead and increases operational efficiency.

A Glance of Feature Development

Asset Management Module

The core of Maintena Sync centers around comprehensive asset tracking:

Asset Register & Job Scheduling

  • Hierarchical asset structure
  • Complete maintenance history with automated scheduling
  • Photo documentation and technical specification documents attachments
  • Calendar and running-hours based scheduling
  • Integration with procurement for parts planning

Future Development

Altough later we realized, that some extra development is required for better user experience for the user, such as mobile support for field technicians to quickly update the status. There are lots of development roadmap that could be integrated, and we’ve wrote them in the in-app user manual.

Few Lessons Learned along Development

Throughout the development of Maintena Sync, I encountered numerous technical challenges that provided valuable insights into enterprise desktop application development. These experiences have shaped my approach to complex software architecture, performance optimization, and maintainable code design.

Working with Qt has provided exceptional experience in solving real-world user problems while maintaining application performance and cross-platform compatibility. While Qt has limitations in certain specific use cases, the framework offers tremendous potential for enterprise desktop development. Although I haven’t yet delivered commercial embedded device applications, I’m eager to explore Qt’s embedded capabilities, building on my previous experience with Arduino and Nextion HMI systems.

Here are the critical technical lessons learned during Maintena’s development:

Deciding the Tech Stack with Available Resource and SRS (System Requirement Specification)

When we first started developing Maintena, we made the common mistake of choosing a technology stack based on what seemed easiest to start with, rather than considering our actual requirements and available resources. This taught us the critical importance of aligning technology choices with both system requirements and team capabilities.

Initially we used PySide, but there were several problems we were facing that became apparent as the application grew in complexity:

The PySide Problems:

  • Lack of dedicated UI designer: Our team didn’t have dedicated designers to create complex interfaces in Figma, which made the UI slicing process painfully slow
  • Memory issues: The app consumed 300MB of memory doing practically nothing, which was unacceptable for a desktop application that needed to run efficiently on vessel computers
  • Limited multi-threading: Python’s GIL (Global Interpreter Lock) made it difficult to implement proper multi-threading for complex background processes
  • Slow data processing: Large datasets processed significantly slower than acceptable for real-time maritime operations

At some point, we decided that the progress was too slow and the app was becoming harder to scale for a complex desktop application using PySide. We had to make the difficult decision to leave some UI aesthetics behind and find a better tech stack that matched our actual requirements.

After brainstorming and evaluating our options, we decided to stick with Qt but switch to C++ since it’s significantly faster and provides true multi-threading capabilities for better processing speed.

The Revelation: As we developed and tested various use cases using Qt Widget and C++, we discovered that most standard interfaces are already provided by Qt out of the box. Features like sorting and filtering, and various model views such as QTableView, QListView, etc., already satisfied our designed use cases perfectly. We realized we had been overthinking the UI complexity when Qt already provided robust, professional-looking components.

// What we discovered: Qt's built-in components are incredibly powerful
QTableView* maintenanceView = new QTableView();
maintenanceView->setSortingEnabled(true);  // Built-in sorting
maintenanceView->setAlternatingRowColors(true);  // Professional appearance
maintenanceView->setSelectionBehavior(QAbstractItemView::SelectRows);

// Model handles data logic, view handles presentation
MaintenanceTableModel* model = new MaintenanceTableModel();
maintenanceView->setModel(model);  // Clean separation of concerns

The Trade-off Reality: One problem we encountered with Qt Widgets was the limited graphing customizability. Honestly, charts look much better using Chart.js in QML compared to Qt Widget’s basic charting capabilities. There was a way around this using QWebEngineView to embed web-based charts, but this approach made the app significantly larger and more resource-intensive.

Key Lesson Learned: The importance of choosing the right tech stack cannot be overstated. You must evaluate your system’s requirements, your team’s available resources and skills, and ensure the development process flows smoothly while meeting your System Requirements Specification (SRS) constraints. Don’t choose technology because it’s trendy or because you want to learn it - choose it because it’s the best tool for your specific job.

Multi-Threading in Qt: The Right Way vs The Wrong Way

Multi-threading in Qt is one of those topics where the documentation examples can actually lead you down the wrong path if you’re not careful. Most developers, myself included, initially follow the obvious approach that seems natural from reading the Qt documentation.

The Common Mistake: Inheriting QThread

When you first read Qt documentation and see examples, it’s natural to inherit from QThread and override the run() method. The documentation shows examples of workers inheriting from QThread, and it seems logical to use QThread’s virtual function to run your process.

However, this approach leads to unpredictable behavior and is prone to errors. The main issue is that while your run() method executes in the new thread, the QObject’s signals and slots still operate in the main thread context unless explicitly moved. This creates a confusing hybrid situation where part of your object runs in one thread and part in another.

The Problem with Thread Inheritance:

// WRONG WAY - This seems logical but creates problems
class WorkerThread : public QThread {
    Q_OBJECT
public:
    void run() override {
        // This runs in the worker thread
        while (!isInterruptionRequested()) {
            processData();  // Worker thread context
            emit dataProcessed();  // Signal emitted from main thread context!
        }
    }
};

The Qt-Recommended Approach: moveToThread()

The better approach is to create a worker class that inherits from QObject, then use moveToThread() to move the entire object to a worker thread. This ensures all of the worker’s methods and signal/slot connections operate consistently within the worker thread context.

Why This Approach Works Better:

  1. Predictable Behavior: All worker methods run in the worker thread
  2. Clean Lifecycle Management: Clear creation, execution, and destruction phases
  3. Thread-Safe Communication: Signals and slots provide safe inter-thread communication
  4. Easier Debugging: Clear separation between main thread and worker thread operations
// CORRECT WAY - Worker object moved to thread
class MaintenanceWorker : public QObject {
    Q_OBJECT
public slots:
    void performMaintenance() {
        // Everything here runs in worker thread
        emit progressUpdate(50);
        // Process maintenance data
        emit finished();
    }
signals:
    void progressUpdate(int percentage);
    void finished();
};

// Thread manager handles the lifecycle
class QThreadManager : public QObject {
    Q_OBJECT
private:
    QThread* workerThread;
    MaintenanceWorker* worker;
    
public:
    QThreadManager() {
        workerThread = new QThread(this);
        worker = new MaintenanceWorker();
        worker->moveToThread(workerThread);
        
        // Connect lifecycle properly
        connect(workerThread, &QThread::started, 
                worker, &MaintenanceWorker::performMaintenance);
        connect(worker, &MaintenanceWorker::finished, 
                workerThread, &QThread::quit);
    }
};

When to Use Advanced Threading Concepts: There are more advanced Qt threading concepts like QSemaphore, QMutex, and QWaitCondition, but for most use cases, the moveToThread pattern with a thread manager handles common scenarios perfectly: background database queries, server communication, file operations, and auto-saving data.

Key Takeaway: Most Qt threading problems can be solved with the moveToThread pattern combined with a thread manager class. This approach provides predictable behavior, proper lifecycle management, and thread-safe communication without the complexity of lower-level threading primitives.

Raw Pointer Issue: The Hidden Dangers of Manual Memory Management

One of the most challenging aspects of developing a complex C++ application is memory management. Initially, we used raw pointers throughout our codebase because they seemed straightforward and we thought we could manage the object lifecycles manually. This decision came back to haunt us as the system grew in complexity.

Why Raw Pointers Seem Appealing Initially: When your application is small and simple, raw pointers appear to work fine. You create objects, pass pointers around, and delete them when you’re done. For small applications with clear, linear object lifecycles, this can work. However, as soon as you have multiple objects sharing references to the same data, the complexity explodes exponentially.

The Reality of Growing Complexity: As our maritime maintenance system grew more sophisticated, we faced increasingly difficult debugging scenarios. Even with professional debugging tools, problems like dangling pointers and unclear object ownership became nightmare scenarios to track down. The object lifecycle became blurred, and we couldn’t reliably determine who was responsible for cleaning up which objects.

The Most Dangerous Problem: Dangling Pointers The most insidious issue we encountered was dangling pointers. In our system, maintenance objects were shared among multiple widgets and components. There was always the likelihood that during one process, a pointer would get deleted while another widget was still processing it. Since multiple components shared the same pointer, object ownership became completely unclear.

The Debugging Nightmare: Even our safety checks failed us. Code like if (pointer != nullptr) didn’t work reliably because the pointer wasn’t actually null after deletion - it often contained garbage values. The memory location might contain random data, making it appear valid when it was actually pointing to freed memory. This led to seemingly random crashes that were extremely difficult to reproduce and fix.

// The dangerous scenario we faced
MaintenanceItem* item = new MaintenanceItem();
ComponentA* compA = new ComponentA();
ComponentB* compB = new ComponentB();

compA->setMaintenanceItem(item);  // Component A thinks it can use this
compB->setMaintenanceItem(item);  // Component B also thinks it can use this

// Later, somewhere in the code...
delete item;  // Whoops! Both components now have dangling pointers

// These operations become dangerous
compA->processItem();  // Might crash, might work, might corrupt data
compB->displayItem();  // Same unpredictable behavior

The Smart Pointer Solution: Eventually, we made the difficult but necessary decision to migrate almost all raw pointers to smart pointers. For QObject-derived classes, we used QPointer for better compatibility with Qt’s object management system.

Why Smart Pointers Solve These Problems:

  1. Automatic Reference Counting: std::shared_ptr automatically tracks how many objects are using a resource
  2. Guaranteed Cleanup: Objects are automatically deleted when the last reference is released
  3. Reliable Null Checks: Null checks actually work because smart pointers properly null themselves
  4. Clear Ownership: std::unique_ptr makes single ownership explicit, std::shared_ptr makes shared ownership safe
  5. Qt Integration: QPointer automatically nulls itself when the referenced QObject is deleted

The Migration Process: This migration took considerable time because pointers were practically everywhere in our codebase, and we weren’t sure about the ownership of many objects. We had to go file by file, refactoring code to support smart pointers and clarifying object ownership relationships that had been ambiguous for months.

The End Result: Eventually, the application became much more persistent and stable. Copying and sharing pointers became safer and easier with smart pointers. Our null checks became reliable, and the app became significantly more resilient. While we didn’t achieve zero bugs (few complex applications do), we eliminated entire categories of crashes and memory-related issues.

Key Lesson: For any C++ application beyond trivial complexity, invest in proper memory management from the beginning. The upfront cost of using smart pointers is far less than the debugging nightmare and refactoring costs of fixing raw pointer issues later.

Performance vs. Aesthetics: Making Practical Choices

One of the most challenging decisions we faced during development was balancing visual appeal with system performance. This dilemma became particularly apparent when we switched from PySide to Qt Widgets and discovered the limitations in graphical customization.

The Aesthetic Compromise: One of the most significant drawbacks of switching to Qt Widgets was the limited graphing customizability compared to modern web technologies. To be completely honest, charts created with Chart.js in a QML environment look significantly more polished and visually appealing than what we could achieve with Qt Widget’s built-in charting capabilities.

The Tempting but Costly Alternative: There was a potential workaround: we could embed Chart.js using QWebEngineView, which essentially runs a Chromium browser engine within our Qt application. We could then use QWebChannel for data transfer between the Qt application and the embedded web content. This would give us beautiful, interactive charts with all the visual polish of modern web applications.

However, after implementing and testing this approach, we discovered it made our application significantly larger (adding 150+ MB due to the Chromium engine) and much more resource-intensive. The memory footprint increased dramatically, and CPU usage was noticeably higher, especially problematic for maritime applications that need to run efficiently on vessel computers with limited resources.

The Pragmatic Decision: For our current use case, we decided the performance trade-off wasn’t worth the aesthetic improvement. Our graphs were still functional - basic plots, bar charts, doughnut charts, etc. - and they served their purpose for maintenance data visualization. The Qt Widget approach provided adequate visual representation while maintaining the performance characteristics essential for maritime operations.

Understanding the Use Case Context: We realized that the choice between aesthetics and performance depends heavily on your application’s context:

  • For complex desktop applications like maritime maintenance systems, Qt Widgets provide better performance, reliability, and resource efficiency
  • For smaller applications focused on visual impact, like car dashboards or consumer HMI interfaces, Qt Quick with QML might be the better choice despite the performance overhead
  • For web-based applications where users expect modern UI interactions, the web technology stack with frameworks like Chart.js makes perfect sense

The Broader Lesson: This experience taught us that technology choices shouldn’t be made based solely on what looks better in screenshots or demos. The most important factors should be:

  1. User needs and expectations in their operational context
  2. Performance requirements for the target environment
  3. Maintenance and development overhead
  4. Long-term scalability and reliability

Maritime professionals using our software care far more about reliability, speed, and resource efficiency than they do about having animated chart transitions or gradient color schemes.

Documentation-Driven Development: Learning from Our Own Mistakes

One of the most humbling lessons we learned was how quickly you can forget your own code, even when working on a solo project. When working in a team environment, this problem multiplies exponentially because each team member might use different approaches, coding styles, design patterns, or algorithms to solve similar problems.

The Individual Developer Problem: Even working alone, we discovered that after a few weeks away from a particular code section, we’d return to find ourselves asking “What was I thinking when I wrote this?” The logic that seemed crystal clear during implementation became opaque and confusing just weeks later. This became a significant productivity killer when we needed to maintain or extend existing functionality.

The Team Collaboration Challenge: The problem becomes much worse in team environments. Even with a shared goal and common tech stack, individual developers naturally approach problems differently. One developer might favor object-oriented patterns while another prefers functional approaches. One might optimize for performance while another optimizes for readability. Without proper documentation, these different approaches create a fragmented, inconsistent codebase that becomes progressively harder to maintain.

What Documentation-Driven Development Actually Means: Documentation-driven development doesn’t just mean adding comments to your code. It means thinking about documentation as a first-class citizen in your development process:

  1. Document Your Decisions: Why did you choose this algorithm over alternatives?
  2. Explain Your Assumptions: What conditions must be true for this code to work correctly?
  3. Provide Usage Examples: How is this class or method intended to be used?
  4. Document Edge Cases: What are the known limitations or special conditions?
  5. Explain Business Context: Why does this technical solution solve the business problem?

The Maritime Context: For our maritime maintenance system, this became especially critical because:

  • Complex Domain Knowledge: Maritime maintenance has specialized rules and regulations that aren’t obvious to developers
  • Safety Implications: Bugs in maintenance software can have serious real-world consequences
  • Long Development Cycles: Features developed months apart need to integrate seamlessly
  • Regulatory Compliance: We need to demonstrate our software logic for regulatory audits

Practical Implementation: We started treating documentation as part of our definition of “done” for any feature. Before code could be considered complete, it needed:

  • Clear API documentation with examples
  • Explanation of business logic and maritime regulations it implements
  • Error handling and edge case documentation
  • Integration examples showing how it fits with existing systems

This approach transformed our development velocity. Instead of spending hours reverse-engineering our own code, we could quickly understand and extend existing functionality.

Separation of Concerns: The Evolution from Monoliths to Maintainable Architecture

One of the most important architectural lessons we learned was understanding when and why to separate different concerns in our codebase. Initially, when our application was smaller and simpler, it seemed perfectly reasonable to put related functionality together in single classes or files. However, as our maritime maintenance system grew in complexity, this approach became a significant maintainability nightmare.

Why Monolithic Approaches Work Initially: When you’re building a small application with limited functionality, putting multiple concerns together actually makes sense:

  • Faster Initial Development: You can quickly prototype and test ideas
  • Less Code Overhead: You don’t need abstract interfaces and dependency injection
  • Easier to Understand: Everything related to a feature is in one place
  • Simpler Debugging: You can trace through all related logic in a single file

For example, a simple maintenance widget might handle database queries, business logic calculations, and UI updates all in the same class, and this works fine when you have 10 maintenance items and basic CRUD operations.

The Growing Complexity Problem: As our maritime maintenance system expanded to handle complex regulatory requirements, multi-vessel fleets, offline synchronization, and sophisticated reporting, the monolithic approach became increasingly problematic:

Testing Becomes Impossible: When database logic, business rules, and UI code are mixed together, you can’t test business logic without also testing database connections and UI components. This makes unit testing extremely difficult and time-consuming.

Code Reuse Becomes Impossible: Business logic embedded in UI classes can’t be reused by other components. If we want to generate the same maintenance compliance calculations for both a dashboard widget and a PDF report, we end up duplicating complex logic across multiple UI classes.

Changes Have Ripple Effects: Modifying database schema requires changes in UI classes. Updating business rules affects presentation logic. Adding new UI elements might require modifying database code. Every change potentially breaks multiple unrelated features.

Debugging Becomes a Nightmare: When a maintenance calculation is wrong, you have to dig through UI event handlers, database queries, and presentation logic all mixed together. Finding the actual bug becomes like searching for a needle in a haystack.

Understanding Separation of Concerns: Separation of Concerns is an architectural principle that suggests different aspects of your application should be handled by different components:

  • Data Access Layer: How you retrieve and persist data
  • Business Logic Layer: The rules and calculations specific to your domain (maritime maintenance)
  • Presentation Layer: How you display information to users
  • Service Layer: Coordination and workflow management

The Practical Benefits: Once we properly separated these concerns in our maritime system:

  1. Independent Testing: We could test maintenance compliance calculations without UI or database dependencies
  2. Code Reuse: The same business logic could power dashboards, reports, and API endpoints
  3. Team Productivity: Different developers could work on UI, database, and business logic simultaneously
  4. Easier Maintenance: Bug fixes in business logic didn’t require UI changes and vice versa
  5. Technology Flexibility: We could change database technologies without affecting business logic

The Maritime-Specific Example: In our system, calculating vessel maintenance compliance involves complex maritime regulations, class society requirements, and flag state rules. By separating this logic into a dedicated service layer:

  • The compliance calculator can be used by maintenance widgets, reporting systems, and API endpoints
  • We can unit test compliance calculations against known regulatory scenarios
  • UI developers don’t need to understand maritime regulations to work on presentation
  • Maritime domain experts can review and validate business logic independently

The Real-World Impact: This architectural change transformed our development process. Instead of spending days debugging UI issues that turned out to be business logic problems, we could quickly isolate issues to their appropriate layers. Adding new features became predictable and manageable rather than a source of anxiety about breaking existing functionality.

Key Takeaway: Start thinking about separation of concerns early in your development process, not after your codebase has become unmanageable. The upfront investment in proper architecture pays dividends in development velocity, code quality, and team productivity.

Implementing Design Patterns and Best Practices in Various Conditions

As our maritime maintenance system grew in complexity, we discovered that textbook design patterns needed to be adapted to real-world conditions and constraints. The key insight was understanding that design patterns are not rigid templates to be copied, but flexible solutions that need to be tailored to specific contexts and requirements.

Strategy Pattern for Conflict Resolution: One of the most critical areas where we needed flexible solutions was handling data synchronization conflicts between vessel and shore systems. Different maritime companies have different policies for handling conflicts - some prioritize shore-based data, others trust vessel reports, and some require manual resolution for critical systems.

Rather than hardcoding a single conflict resolution approach, we implemented the Strategy Pattern to make this decision configurable and extensible. This allowed us to support different client requirements without modifying core synchronization logic.

Observer Pattern for Real-Time Maritime Operations: Maritime operations require immediate notification of critical maintenance events. A main engine failure or safety equipment malfunction needs instant attention from multiple systems: the maintenance dashboard, notification services, compliance tracking, and reporting systems.

We implemented the Observer Pattern to decouple these systems while ensuring reliable communication. When a maintenance event occurs, all interested components are automatically notified without the maintenance service needing to know about each consumer.

Factory Pattern for Multi-Environment Deployment: Our system needed to run in various environments: local SQLite databases on vessels, MySQL clusters in shore offices, and PostgreSQL systems for enterprise clients. Rather than hardcoding database connections, we used the Factory Pattern to create appropriate database connections based on deployment configuration.

Bridge Pattern for Cross-Platform Compatibility: Since our Qt application needed to run on Windows (most common on vessels), Linux (shore servers), and macOS (office environments), we used the Bridge Pattern to abstract platform-specific operations like file handling, networking, and system integration.

The Key Insight - Context Matters: The most important lesson was understanding that design patterns must be adapted to your specific domain and constraints:

  1. Maritime Regulations: Our patterns needed to support audit trails and regulatory compliance
  2. Offline Operations: Patterns had to work when vessels lost internet connectivity
  3. Safety Requirements: Critical maintenance systems couldn’t fail silently
  4. Performance Constraints: Vessel computers have limited resources compared to shore systems

When NOT to Use Design Patterns: We also learned that design patterns can be overused. Not every piece of code needs a pattern. Simple, direct solutions are often better than over-engineered pattern implementations, especially for straightforward CRUD operations or simple UI interactions.

The Evolution of Our Architecture: Our architecture evolved from simple, monolithic classes to a well-structured system using appropriate patterns where they added value. The key was implementing patterns gradually as complexity demanded them, rather than trying to design the perfect architecture upfront.

This iterative approach allowed us to learn from real-world usage patterns and adapt our design decisions based on actual requirements rather than theoretical perfect solutions.

The Maritime Industry Lesson: Working in the maritime industry taught us that software architecture must reflect real-world operational constraints. Design patterns that work well in web applications might need significant adaptation for offline-capable, safety-critical maritime systems. The best architecture is one that solves actual problems rather than demonstrating theoretical knowledge.

Business Model

Commercial Viability

  • SaaS Licensing: Recurring revenue model with tiered features
  • Custom Implementation: Enterprise solutions with specific requirements
  • Support Contracts: Ongoing maintenance and feature development

Future Enhancement Roadmap

Planned Features

  • Mobile Applications: iOS and Android companions for field work
  • IoT Integration: Sensor data collection and analysis
  • AI-Powered Insights: Predictive maintenance algorithms
  • Advanced Reporting: Business intelligence and analytics

Market Expansion

  • Industry Adaptation: Merchant Vessel, Manufacturing, facilities management, transportation
  • Geographic Expansion: Localization for international markets
  • Partnership Opportunities: Integration with existing enterprise systems

Conclusion: From Survey, Code, to Commerce

Maintena Sync demonstrates how understanding real-world problems can drive successful software development. The combination of solid technical architecture, user-centered design, and iterative development resulted in a commercial product that’s genuinely improving operational efficiency for its users.

Key Success Factors:

  • Deep domain knowledge driving feature priorities
  • Robust technical architecture supporting scalability
  • User feedback integration throughout development
  • Commercial viability from initial design
  • Sense of Belonging of a Product amont teams

The project showcases rigorous developments in enterprise desktop application, from complex database design, performance issue, to user experience optimization. Most importantly, it proves the ability to deliver complete business solutions that create measurable value, instead of scattered paper-based asset managements, job scheduling, etc. for a merchant vessel and across fleet.


Technical Specifications:

  • Frontend: Qt6 C++ with custom widgets
  • Backend: Multi-threaded C++ application logic
  • Database: SQLite in local and MySQL in cloud
  • Deployment: Cross-platform installers (Currently released on Windows only)
  • Security: Role-based access with encrypted data storage (AES-256 for local encryption)