|
39 | 39 | - [Tenant-Aware Configuration](#tenant-aware-configuration) |
40 | 40 | - [Tenant Configuration Loading](#tenant-configuration-loading) |
41 | 41 | - [Error Handling](#error-handling) |
| 42 | +- [Debugging and Troubleshooting](#debugging-and-troubleshooting) |
| 43 | + - [Module Interface Debugging](#module-interface-debugging) |
| 44 | + - [Common Issues](#common-issues) |
| 45 | + - [Diagnostic Tools](#diagnostic-tools) |
42 | 46 | - [Common Error Types](#common-error-types) |
43 | 47 | - [Error Wrapping](#error-wrapping) |
44 | 48 | - [Testing Modules](#testing-modules) |
@@ -717,48 +721,196 @@ if err := doSomething(); err != nil { |
717 | 721 |
|
718 | 722 | This allows for error inspection using `errors.Is` and `errors.As`. |
719 | 723 |
|
720 | | -## Testing Modules |
| 724 | +## Debugging and Troubleshooting |
721 | 725 |
|
722 | | -### Mock Application |
| 726 | +The Modular framework provides several debugging utilities to help diagnose common issues with module lifecycle, interface implementation, and service injection. |
723 | 727 |
|
724 | | -Modular facilitates testing with mock implementations: |
| 728 | +### Module Interface Debugging |
| 729 | + |
| 730 | +#### DebugModuleInterfaces |
| 731 | + |
| 732 | +Use `DebugModuleInterfaces` to check which interfaces a specific module implements: |
725 | 733 |
|
726 | 734 | ```go |
727 | | -// Create a mock application |
728 | | -mockApp := &MockApplication{ |
729 | | - configSections: make(map[string]modular.ConfigProvider), |
730 | | - services: make(map[string]any), |
731 | | -} |
| 735 | +import "github.com/GoCodeAlone/modular" |
| 736 | + |
| 737 | +// Debug a specific module |
| 738 | +modular.DebugModuleInterfaces(app, "your-module-name") |
| 739 | +``` |
| 740 | + |
| 741 | +**Output example:** |
| 742 | +``` |
| 743 | +🔍 Debugging module 'web-server' (type: *webserver.Module) |
| 744 | + Memory address: 0x14000026840 |
| 745 | + ✅ Module |
| 746 | + ✅ Configurable |
| 747 | + ❌ DependencyAware |
| 748 | + ✅ ServiceAware |
| 749 | + ✅ Startable |
| 750 | + ✅ Stoppable |
| 751 | + ❌ Constructable |
| 752 | + 📦 Provides 1 services, Requires 0 services |
| 753 | +``` |
| 754 | + |
| 755 | +#### DebugAllModuleInterfaces |
732 | 756 |
|
733 | | -// Register test services |
734 | | -mockApp.RegisterService("database", &MockDatabase{}) |
| 757 | +Debug all registered modules at once: |
735 | 758 |
|
736 | | -// Test your module |
737 | | -module := NewMyModule() |
738 | | -err := module.Init(mockApp) |
739 | | -assert.NoError(t, err) |
| 759 | +```go |
| 760 | +// Debug all modules in the application |
| 761 | +modular.DebugAllModuleInterfaces(app) |
740 | 762 | ``` |
741 | 763 |
|
742 | | -### Testing Services |
| 764 | +#### CompareModuleInstances |
743 | 765 |
|
744 | | -Test service implementations to ensure they meet interface requirements: |
| 766 | +Compare module instances before and after initialization to detect if modules are being replaced: |
745 | 767 |
|
746 | 768 | ```go |
747 | | -func TestDatabaseService(t *testing.T) { |
748 | | - // Create mock DB |
749 | | - db := &MockDB{} |
| 769 | +// Store reference before initialization |
| 770 | +originalModule := app.moduleRegistry["module-name"] |
| 771 | + |
| 772 | +// After initialization |
| 773 | +currentModule := app.moduleRegistry["module-name"] |
| 774 | + |
| 775 | +modular.CompareModuleInstances(originalModule, currentModule, "module-name") |
| 776 | +``` |
| 777 | + |
| 778 | +### Common Issues |
| 779 | + |
| 780 | +#### 1. "Module does not implement Startable, skipping" |
| 781 | + |
| 782 | +**Symptoms:** Module has a `Start` method but is reported as not implementing `Startable`. |
| 783 | + |
| 784 | +**Common Causes:** |
| 785 | + |
| 786 | +1. **Incorrect method signature** - Most common issue: |
| 787 | + ```go |
| 788 | + // ❌ WRONG - missing context parameter |
| 789 | + func (m *MyModule) Start() error { return nil } |
| 790 | + |
| 791 | + // ✅ CORRECT |
| 792 | + func (m *MyModule) Start(ctx context.Context) error { return nil } |
| 793 | + ``` |
| 794 | + |
| 795 | +2. **Wrong context import:** |
| 796 | + ```go |
| 797 | + // ❌ WRONG - old context package |
| 798 | + import "golang.org/x/net/context" |
| 799 | + |
| 800 | + // ✅ CORRECT - standard library |
| 801 | + import "context" |
| 802 | + ``` |
| 803 | + |
| 804 | +3. **Constructor returns module without Startable interface:** |
| 805 | + ```go |
| 806 | + // ❌ PROBLEMATIC - returns different type |
| 807 | + func (m *MyModule) Constructor() ModuleConstructor { |
| 808 | + return func(app Application, services map[string]any) (Module, error) { |
| 809 | + return &DifferentModuleType{}, nil // Lost Startable! |
| 810 | + } |
| 811 | + } |
| 812 | + |
| 813 | + // ✅ CORRECT - preserves all interfaces |
| 814 | + func (m *MyModule) Constructor() ModuleConstructor { |
| 815 | + return func(app Application, services map[string]any) (Module, error) { |
| 816 | + return m, nil // Or create new instance with all interfaces |
| 817 | + } |
| 818 | + } |
| 819 | + ``` |
| 820 | + |
| 821 | +#### 2. Service Injection Failures |
| 822 | + |
| 823 | +**Symptoms:** `"failed to inject services for module"` errors. |
| 824 | + |
| 825 | +**Debugging steps:** |
| 826 | +1. Verify service names match exactly |
| 827 | +2. Check that required services are provided by other modules |
| 828 | +3. Ensure dependency order is correct |
| 829 | +4. Use interface-based matching for flexibility |
| 830 | + |
| 831 | +#### 3. Module Replacement Issues |
| 832 | + |
| 833 | +**Symptoms:** Module works before `Init()` but fails after. |
| 834 | + |
| 835 | +**Cause:** Constructor-based injection replaces the original module instance. |
| 836 | + |
| 837 | +**Solution:** Ensure your Constructor returns a module that implements all the same interfaces. |
| 838 | + |
| 839 | +### Diagnostic Tools |
| 840 | + |
| 841 | +#### CheckModuleStartableImplementation |
| 842 | + |
| 843 | +For detailed analysis of why a module doesn't implement Startable: |
| 844 | + |
| 845 | +```go |
| 846 | +import "github.com/GoCodeAlone/modular" |
| 847 | + |
| 848 | +// Check specific module |
| 849 | +modular.CheckModuleStartableImplementation(yourModule) |
| 850 | +``` |
| 851 | + |
| 852 | +**Output includes:** |
| 853 | +- Method signature analysis |
| 854 | +- Expected vs actual parameter types |
| 855 | +- Interface compatibility check |
| 856 | + |
| 857 | +#### Example Debugging Workflow |
| 858 | + |
| 859 | +When troubleshooting module issues: |
| 860 | + |
| 861 | +```go |
| 862 | +func debugModuleIssues(app *modular.StdApplication) { |
| 863 | + // 1. Check all modules before initialization |
| 864 | + fmt.Println("=== BEFORE INIT ===") |
| 865 | + modular.DebugAllModuleInterfaces(app) |
| 866 | + |
| 867 | + // 2. Store references to original modules |
| 868 | + originalModules := make(map[string]modular.Module) |
| 869 | + for name, module := range app.SvcRegistry() { |
| 870 | + originalModules[name] = module |
| 871 | + } |
750 | 872 |
|
751 | | - // Verify it implements the interface |
752 | | - var _ DatabaseService = db |
| 873 | + // 3. Initialize |
| 874 | + err := app.Init() |
| 875 | + if err != nil { |
| 876 | + fmt.Printf("Init error: %v\n", err) |
| 877 | + } |
753 | 878 |
|
754 | | - // Test specific methods |
755 | | - err := db.Connect() |
756 | | - assert.NoError(t, err) |
| 879 | + // 4. Check modules after initialization |
| 880 | + fmt.Println("=== AFTER INIT ===") |
| 881 | + modular.DebugAllModuleInterfaces(app) |
757 | 882 |
|
758 | | - result, err := db.Query("SELECT 1") |
759 | | - assert.NoError(t, err) |
760 | | - assert.NotNil(t, result) |
| 883 | + // 5. Compare instances |
| 884 | + for name, original := range originalModules { |
| 885 | + if current, exists := app.SvcRegistry()[name]; exists { |
| 886 | + modular.CompareModuleInstances(original, current, name) |
| 887 | + } |
| 888 | + } |
| 889 | + |
| 890 | + // 6. Check specific problematic modules |
| 891 | + if problematicModule, exists := app.SvcRegistry()["problematic-module"]; exists { |
| 892 | + modular.CheckModuleStartableImplementation(problematicModule) |
| 893 | + } |
761 | 894 | } |
762 | 895 | ``` |
763 | 896 |
|
764 | | -By using dependency injection and interfaces, Modular makes it easy to test modules in isolation. |
| 897 | +#### Best Practices for Debugging |
| 898 | + |
| 899 | +1. **Add debugging early:** Use debugging utilities during development, not just when issues occur. |
| 900 | + |
| 901 | +2. **Check before and after Init():** Many issues occur during the initialization phase when modules are replaced via constructors. |
| 902 | + |
| 903 | +3. **Verify method signatures:** Double-check that your Start/Stop methods match the expected interface signatures exactly. |
| 904 | + |
| 905 | +4. **Use specific error messages:** The debugging tools provide detailed information about why interfaces aren't implemented. |
| 906 | + |
| 907 | +5. **Test interface implementations:** Add compile-time checks to ensure your modules implement expected interfaces: |
| 908 | + ```go |
| 909 | + // Compile-time interface check |
| 910 | + var _ modular.Startable = (*MyModule)(nil) |
| 911 | + var _ modular.Stoppable = (*MyModule)(nil) |
| 912 | + ``` |
| 913 | + |
| 914 | +6. **Check memory addresses:** If memory addresses differ before and after Init(), your module was replaced by a constructor. |
| 915 | + |
| 916 | +By using these debugging tools and following these practices, you can quickly identify and resolve module interface and lifecycle issues in your Modular applications. |
0 commit comments