Skip to content

Commit 1fc4e87

Browse files
authored
Merge pull request #26 from fbz-tec/feature/refactor-architecture
Feature/refactor architecture
2 parents 7bd5124 + c649f38 commit 1fc4e87

33 files changed

+975
-730
lines changed

.github/workflows/ci.yml

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,24 @@ jobs:
7070
set -e
7171
mkdir -p build
7272
73-
# Compute short commit hash
73+
# Compute version info
7474
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
7575
VERSION="dev-${GIT_COMMIT}"
76+
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
77+
MODULE="github.com/fbz-tec/pgxport"
78+
79+
# ldflags for internal/version package
80+
LDFLAGS="-X ${MODULE}/internal/version.AppVersion=${VERSION} \
81+
-X ${MODULE}/internal/version.GitCommit=${GIT_COMMIT} \
82+
-X ${MODULE}/internal/version.BuildTime=${BUILD_TIME}"
83+
84+
echo "Building with version: ${VERSION}"
7685
7786
# Build Linux binary
78-
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-X main.Version=${VERSION} \
79-
-X main.GitCommit=${GIT_COMMIT} \
80-
-X main.BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
81-
-o build/pgxport
82-
87+
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o build/pgxport
8388
8489
# Build Windows binary
85-
GOOS=windows GOARCH=amd64 go build -ldflags="-X main.Version=${VERSION} \
86-
-X main.GitCommit=${GIT_COMMIT} \
87-
-X main.BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
88-
-o build/pgxport.exe
90+
GOOS=windows GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o build/pgxport.exe
8991
9092
- name: 📤 Upload Linux artifact
9193
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')
@@ -104,7 +106,7 @@ jobs:
104106
retention-days: 3
105107

106108
- name: 🧹 Cleanup build directory
107-
if: always() # Ensures cleanup runs even if previous step fails
109+
if: always()
108110
run: |
109111
echo "🧹 Cleaning up build artifacts..."
110112
rm -rf build
@@ -133,7 +135,12 @@ jobs:
133135
VERSION="${{ github.ref_name }}"
134136
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
135137
GIT_COMMIT=$(git rev-parse --short HEAD)
136-
LDFLAGS="-s -w -X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT}"
138+
MODULE="github.com/fbz-tec/pgxport"
139+
140+
LDFLAGS="-s -w \
141+
-X ${MODULE}/internal/version.AppVersion=${VERSION} \
142+
-X ${MODULE}/internal/version.BuildTime=${BUILD_TIME} \
143+
-X ${MODULE}/internal/version.GitCommit=${GIT_COMMIT}"
137144
138145
echo "Building binaries for version ${VERSION}..."
139146
@@ -202,9 +209,10 @@ jobs:
202209
- name: 🧮 Generate checksums
203210
run: |
204211
cd dist
205-
for file in *; do
206-
sha256sum "$file" > "$file.sha256"
207-
done
212+
sha256sum *.tar.gz *.zip > checksums.txt
213+
echo ""
214+
echo "✅ Checksums generated:"
215+
cat checksums.txt
208216
209217
- name: 🚀 Create GitHub Release
210218
uses: softprops/action-gh-release@v2
@@ -240,4 +248,4 @@ jobs:
240248
dist/*.zip
241249
dist/checksums.txt
242250
env:
243-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
251+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,15 @@ This is the first pre-release of pgxport.
2121
- Custom date/time formats and timezone support
2222
- SQL export with schema-qualified table names
2323
- Batch INSERT statements for SQL exports (`--insert-batch`) for improved import performance
24-
- Comprehensive test coverage
25-
- CI/CD pipeline with automated builds
2624

2725
#### Installation
2826

2927
```bash
3028
go install github.com/fbz-tec/[email protected]
3129
```
3230

33-
Or download pre-built binaries from [GitHub Releases](https://github.com/fbz-tec/pgxport/releases/tag/untagged-3731b225ccbb85fa3000).
31+
Or download pre-built binaries from [GitHub Releases](https://github.com/fbz-tec/pgxport/releases/tag/v1.0.0-rc1).
32+
3433

3534
---
3635

README.md

Lines changed: 129 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ pgxport [command] [flags]
153153
| `--delimiter` | `-d` | CSV delimiter character | `,` | No |
154154
| `--no-header` | - | Skip CSV header row in output | `false` | No |
155155
| `--with-copy` | - | Use PostgreSQL native COPY for CSV export (faster for large datasets) | `false` | No |
156-
| `--xml-root-tag` | - | Sets the root XML element name | `results` | No |
157-
| `--xml-row-tag` | - | Sets the row XML element name | `row` | No |
156+
| `--xml-root-tag` | - | Sets the root element name for XML exports | `results` | No |
157+
| `--xml-row-tag` | - | Sets the row element name for XML exports | `row` | No |
158158
| `--fail-on-empty` | - | Exit with error if query returns 0 rows | `false` | No |
159159
| `--table` | `-t` | Table name for SQL INSERT exports (supports schema.table) | - | For SQL format |
160160
| `--insert-batch` | - | Number of rows per INSERT statement for SQL exports | `1` | No |
@@ -626,69 +626,128 @@ INSERT INTO "users" ("id", "name", "email", "created_at") VALUES
626626

627627
```
628628
pgxport/
629-
├── exporters/ # Modular export package
630-
│ ├── exporter.go # Interface and factory
631-
│ ├── compression.go # Compression writers (gzip,zip)
632-
│ ├── common.go # Shared utilities
633-
│ ├── csv_exporter.go # CSV export implementation
634-
│ ├── json_exporter.go# JSON export implementation
635-
│ ├── xml_exporter.go # XML export implementation
636-
│ └── sql_exporter.go # SQL export implementation
637-
├── logger/ # Logging package
638-
│ └── logger.go # Logger interface and implementation
639-
├── main.go # CLI entry point and orchestration
640-
├── config.go # Configuration management with validation
641-
├── store.go # Database operations (connection, queries)
642-
├── version.go # Version information
643-
├── go.mod # Go module definition
644-
├── go.sum # Go module checksums
645-
├── LICENSE # MIT license file
646-
└── README.md # Documentation
629+
├── cmd/ # CLI entry points
630+
│ ├── root.go # Main command + flags
631+
│ ├── root_test.go
632+
│ └── version.go # Version subcommand
633+
634+
├── core/ # Business logic
635+
│ ├── exporter/ # Export formats (pluggable)
636+
│ │ ├── registry.go # Format registration system
637+
│ │ ├── formatting.go # Shared formatting utilities
638+
│ │ ├── compression.go # Compression support (gzip/zip)
639+
│ │ ├── options.go # Export options struct
640+
│ │ ├── testing_helpers.go
641+
│ │ ├── csv_exporter.go # CSV export implementation
642+
│ │ ├── json_exporter.go # JSON export implementation
643+
│ │ ├── xml_exporter.go # XML export implementation
644+
│ │ └── sql_exporter.go # SQL export implementation
645+
│ │
646+
│ ├── db/ # Database operations
647+
│ │ ├── connection.go # PostgreSQL connection management
648+
│ │ └── connection_test.go
649+
│ │
650+
│ ├── config/ # Configuration management
651+
│ │ ├── config.go # Config loading with validation
652+
│ │ └── config_test.go
653+
│ │
654+
│ └── validation/ # Input validation
655+
│ ├── query_safety.go # Query and parameter validation
656+
│ └── query_safety_test.go
657+
658+
├── internal/ # Private packages
659+
│ ├── logger/ # Logging utilities
660+
│ │ └── logger.go # Structured logging with verbose mode
661+
│ └── version/ # Build information
662+
│ └── version.go # Version, BuildTime, GitCommit
663+
664+
├── main.go # Application entry point
665+
├── go.mod # Go module definition
666+
├── go.sum # Go module checksums
667+
├── Taskfile.yml # Build automation
668+
├── LICENSE # MIT license
669+
└── README.md # This file
647670
```
648671

649672
## 🧩 Architecture
650673

651-
The project follows a clean, modular architecture with separated concerns:
674+
The project follows a clean, layered architecture with clear separation of concerns:
652675

653676
```mermaid
654677
flowchart TD
655-
A[CLI - Cobra] --> B[main.go<br/>Orchestration]
656-
B --> C[config.go<br/>Configuration]
657-
B --> D[store.go<br/>DB Operations]
658-
B --> E[exporters/<br/>Export Logic]
678+
A[CLI - Cobra] --> B[cmd/root.go<br/>Command Handler]
679+
B --> C[core/config<br/>Configuration]
680+
B --> D[core/db<br/>DB Connection]
681+
B --> E[core/exporter<br/>Export Logic]
659682
660-
E --> E1[CSV Exporter]
661-
E --> E2[JSON Exporter]
662-
E --> E3[XML Exporter]
663-
E --> E4[SQL Exporter]
683+
E --> E0[registry.go<br/>Format Registry]
684+
E0 --> E1[CSV Exporter]
685+
E0 --> E2[JSON Exporter]
686+
E0 --> E3[XML Exporter]
687+
E0 --> E4[SQL Exporter]
664688
665-
E --> F[compression.go<br/>gzip/zip]
666-
E --> G[common.go<br/>Shared Utils]
689+
E --> F[formatting.go<br/>Shared Utils]
690+
E --> G[compression.go<br/>gzip/zip]
667691
668-
B --> H[logger/<br/>Logging]
692+
B --> H[internal/logger<br/>Logging]
693+
B --> I[internal/version<br/>Build Info]
694+
695+
D --> J[core/validation<br/>Query Safety]
669696
670697
style B fill:#e1f5ff
671698
style E fill:#ffe1f5
672699
style D fill:#f5ffe1
700+
style C fill:#fff4e1
673701
```
674702

703+
**Architecture Principles:**
704+
705+
- **Layered Structure**: Clear separation between CLI, business logic, and utilities
706+
- **Pluggable Exporters**: Registry pattern allows easy addition of new formats
707+
- **SOLID Principles**: Each package has a single, well-defined responsibility
708+
- **Testability**: Modular design facilitates comprehensive testing
709+
675710
**Component Descriptions:**
676711

677-
- **`exporters/`**: Modular export package with Strategy pattern
678-
- **`exporter.go`**: Defines the `Exporter` interface and factory
679-
- **`compression.go`**: Handles output compression (gzip, zip)
680-
- **`common.go`**: Shared formatting utilities for all exporters
681-
- **`csv_exporter.go`**: CSV export implementation
682-
- **`json_exporter.go`**: JSON export implementation
683-
- **`xml_exporter.go`**: XML export implementation
684-
- **`sql_exporter.go`**: SQL INSERT export implementation
685-
- **`logger/`**: Logging package with structured output
686-
- **`logger.go`**: Logger interface and singleton implementation with debug/verbose support
687-
- **`store.go`**: Handles all database operations (connect, query, return results)
688-
- **`main.go`**: Orchestrates the flow between store and exporters
689-
- **`config.go`**: Manages configuration with validation, defaults, and `.env` file loading
690-
691-
Each exporter is isolated in its own file, making the codebase easy to maintain, test, and extend with new formats.
712+
### CLI Layer (`cmd/`)
713+
- **`root.go`**: Main command orchestration with Cobra framework
714+
- **`version.go`**: Version information subcommand
715+
716+
### Core Business Logic (`core/`)
717+
718+
**`exporter/`** - Export format implementations
719+
- **`registry.go`**: Dynamic format registration using factory pattern
720+
- **`formatting.go`**: Shared formatting utilities (dates, escaping, etc.)
721+
- **`compression.go`**: Output compression (gzip, zip)
722+
- **`options.go`**: Export configuration options
723+
- **`csv_exporter.go`**: CSV format with COPY mode support
724+
- **`json_exporter.go`**: JSON array format
725+
- **`xml_exporter.go`**: XML format with customizable tags
726+
- **`sql_exporter.go`**: SQL INSERT statements with batch support
727+
728+
**`db/`** - PostgreSQL operations
729+
- **`connection.go`**: Database connection management and query execution
730+
731+
**`config/`** - Application configuration
732+
- **`config.go`**: Configuration loading with `.env` support and validation
733+
734+
**`validation/`** - Input validation
735+
- **`query_safety.go`**: Query and parameter validation
736+
737+
### Internal Utilities (`internal/`)
738+
739+
**`logger/`** - Structured logging
740+
- **`logger.go`**: Logger implementation with verbose mode support
741+
742+
**`version/`** - Build metadata
743+
- **`version.go`**: Version information set via ldflags during build
744+
745+
### Key Design Patterns
746+
747+
1. **Registry Pattern**: Exporters self-register at init time, enabling dynamic format support
748+
2. **Factory Pattern**: Each export creates a fresh instance, avoiding state sharing
749+
3. **Strategy Pattern**: Exporters implement a common interface for interchangeable behavior
750+
4. **Dependency Injection**: Components receive dependencies rather than creating them
692751

693752
## 🛠️ Development
694753

@@ -713,9 +772,13 @@ The project uses the following main dependencies:
713772

714773
```bash
715774
go mod download
716-
go mod tidy
717775
```
718776

777+
The project structure follows clean architecture principles:
778+
- `cmd/` - CLI commands and flags
779+
- `core/` - Business logic (exporter, database, config, validation)
780+
- `internal/` - Private utilities (logger, version)
781+
719782
**3. Configure your database**
720783

721784
Create a `.env` file:
@@ -744,7 +807,17 @@ go build -o pgxport
744807
go build -o pgxport
745808

746809
# Build with version information
747-
go build -ldflags="-X main.Version=1.0.0" -o pgxport
810+
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
811+
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
812+
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
813+
814+
go build -ldflags="-X github.com/fbz-tec/pgxport/internal/version.AppVersion=${VERSION} \
815+
-X github.com/fbz-tec/pgxport/internal/version.BuildTime=${BUILD_TIME} \
816+
-X github.com/fbz-tec/pgxport/internal/version.GitCommit=${GIT_COMMIT}" \
817+
-o pgxport
818+
819+
# Using Taskfile (recommended)
820+
task build
748821

749822
# Cross-platform builds
750823
GOOS=linux GOARCH=amd64 go build -o pgxport-linux
@@ -844,9 +917,13 @@ Contributions are welcome! Please feel free to submit a Pull Request.
844917

845918
- Follow Go conventions and use `gofmt`
846919
- Add comments for exported functions
847-
- Keep functions small and focused
848-
- Separate concerns (database vs export logic)
849-
- Write tests for new features
920+
- Keep functions small and focused (single responsibility principle)
921+
- Follow the layered architecture:
922+
- `cmd/` - CLI logic only
923+
- `core/` - Business logic
924+
- `internal/` - Reusable utilities
925+
- New export formats should implement the `Exporter` interface and register via `registry.go`
926+
- Write tests for new features (`*_test.go` files alongside source)
850927

851928
## 📄 License
852929

Taskfile.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ vars:
88
sh: date -u '+%Y-%m-%d_%H:%M:%S'
99
GIT_COMMIT:
1010
sh: git rev-parse --short HEAD 2>/dev/null || echo "unknown"
11-
LDFLAGS: -ldflags "-X main.Version={{.VERSION}} -X main.BuildTime={{.BUILD_TIME}} -X main.GitCommit={{.GIT_COMMIT}}"
11+
LDFLAGS: -ldflags "-X github.com/fbz-tec/pgxport/internal/version.AppVersion={{.VERSION}} -X github.com/fbz-tec/pgxport/internal/version.BuildTime={{.BUILD_TIME}} -X github.com/fbz-tec/pgxport/internal/version.GitCommit={{.GIT_COMMIT}}"
1212

1313
tasks:
1414
default:

0 commit comments

Comments
 (0)