Trong quá trình phát triển phần mềm, chúng ta thường viết những đoạn code có thể tái sử dụng nhiều lần:
- Các hàm thống kê
- Utility functions
- Helper cho ML pipelines
- Feature engineering
Tuy nhiên nếu các đoạn code này nằm rải rác trong nhiều repository, việc bảo trì sẽ trở nên khó khăn.
Giải pháp tốt nhất là đóng gói chúng thành một thư viện (library) và phát hành dưới dạng NuGet package.
Trong bài viết này, chúng ta sẽ đi qua toàn bộ quy trình:
Idea → Library → GitHub → CI/CD → NuGet
Dựa trên kinh nghiệm thực tế khi xây dựng thư viện QuantEdge.
Vì sao nên tạo NuGet Package?
NuGet là hệ thống quản lý package chính thức của .NET ecosystem.Việc đóng gói code thành NuGet mang lại nhiều lợi ích.
- Code Reusability
- Easy Distribution
- Version Control
NuGet hỗ trợ Semantic Versioning:
1.0.0 → first version
1.0.1 → bug fix
1.1.0 → add feature
2.0.0 → breaking change
Thiết kế kiến trúc project
Trước khi viết code, nên tổ chức repository theo cấu trúc chuẩn.Ví dụ:
QuantEdge
│
├─ src
│ └─ QuantEdge
│ QuantEdge.csproj
│
├─ tests
│ └─ QuantEdge.Tests
│
├─ docs
│
├─ .github
│ └─ workflows
│
├─ QuantEdge.slnx
├─ README.md
└─ LICENSENguyên tắc quan trọng:
- Prefer static classes
- Prefer pure functions
- Avoid unnecessary state
Cấu hình file .csproj
Để package hiển thị chuyên nghiệp trên NuGet, cần thêm metadata vào.csproj.
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PackageId>YourRepo</PackageId>
<Version>1.0.0</Version>
<Authors>YourName</Authors>
<Description>
Quantitative analytics utilities for .NET developers.
</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>
https://github.com/yourname/your-repo
</RepositoryUrl>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
Lưu ý: khi bạn muốn publish version mới, bạn vào file csproj, cập nhật lại tag Version
Unit Testing
Với thư viện tính toán, unit test là bắt buộc. Framework phổ biến nhất trong .NET hiện nay là xUnit.netVí dụ
public class CorrelationTests
{
[Fact]
public void PerfectPositiveCorrelation()
{
double[] x = {1,2,3,4};
double[] y = {2,4,6,8};
var corr = Correlation.Pearson(x,y);
Assert.Equal(1, corr, 5);
}
}
Build NuGet Package
Sau khi viết xong thư viện, có thể tạo package bằng:dotnet pack -c Release
Hoặc bạn có thể set GeneratePackageOnBuild ở file *.csproj
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
Kết quả:
bin/Release/QuantEdge.1.0.0.nupkg
Đây chính là file NuGet package.
Publish lên NuGet.org
Bạn cần tạo API keySau khi test xong, bạn có thể publish package.
dotnet nuget push bin/Release/QuantEdge.1.0.0.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json
Package sẽ xuất hiện trên NuGet trong vài phút.
Bạn có thể vào trang nuget để kiểm tra trạng thái package đã publish lên
Tự động hóa với GitHub Actions
Để tránh publish thủ công, chúng ta có thể dùng CI/CD.Ví dụ workflow:
Build source code:name: Build
on:
push:
branches: [ main ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --configuration Release
Publish:
name: Publish NuGet
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build -c Release
- name: Pack
run: dotnet pack -c Release -o ./artifacts
- name: Publish
run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
Bạn cần tạo:
git tag v1.0.0
git push origin v1.0.0
Tự động hóa Version với Git Tag
Ở phần trước, chúng ta khai báo version trực tiếp trong file .csproj.
Điều này hoạt động tốt với project nhỏ, nhưng khi CI/CD được áp dụng,
việc sửa version thủ công trở nên bất tiện.
Giải pháp phổ biến là để Git Tag trở thành nguồn quản lý version duy nhất. GitHub Actions sẽ tự động đọc tag và truyền version vào quá trình build.
Code → Git Tag → CI Pipeline → NuGet Package
Version không nên nằm rải rác trong code. Version nên được quản lý ở Git level.
Cập nhật file .csproj
Thay vì hard-code version, ta cho phép nhận version từ bên ngoài.
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version Condition="'$(VERSION)' != ''">$(VERSION)</Version>
<Version Condition="'$(VERSION)' == ''">1.0.0</Version>
<Authors>anbinhtrong</Authors>
</PropertyGroup>
Ý nghĩa:
- Nếu pipeline truyền biến
VERSION→ package sẽ dùng version đó - Nếu không có biến → mặc định dùng
1.0.0
Điều này giúp project vẫn build được khi chạy local.
Biến môi trường trong GitHub Actions
GitHub Actions cung cấp nhiều environment variables chứa thông tin về repository, commit, branch và workflow.
Các biến này rất hữu ích để tự động build, versioning hoặc deploy.
Một số biến quan trọng
GITHUB_REPOSITORY– tên repositoryGITHUB_SHA– commit hashGITHUB_REF– ref đầy đủ (branch hoặc tag)GITHUB_REF_NAME– tên branch hoặc tagGITHUB_RUN_NUMBER– số lần workflow chạyRUNNER_OS– hệ điều hành của runner
Ví dụ nếu bạn tạo tag:
git tag v1.0.3
GitHub Actions sẽ nhận được:
GITHUB_REF = refs/tags/v1.0.3
GITHUB_REF_NAME = v1.0.3
Trích xuất Version từ Git Tag
Chúng ta có thể sử dụng biến GITHUB_REF_NAME để lấy version từ tag.
- name: Extract version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
Nếu tag là v1.2.0, pipeline sẽ tạo biến:
VERSION = 1.2.0
Truyền Version vào dotnet build
Sau khi có biến VERSION, ta truyền nó vào lệnh build và pack.
- name: Build
run: dotnet build -c Release /p:Version=$VERSION
- name: Pack
run: dotnet pack -c Release -o ./artifacts /p:Version=$VERSION
Khi đó NuGet package được tạo ra sẽ có version chính xác từ Git Tag.
QuantEdge.1.2.0.nupkg
Quy trình release mới
Sau khi thiết lập xong, việc phát hành version mới chỉ cần một bước duy nhất:
git tag v1.2.0
git push origin v1.2.0
GitHub Actions sẽ tự động:
- Detect Git Tag
- Extract Version
- Build library
- Pack NuGet package
- Publish lên NuGet.org
Git Tag đóng vai trò như một "release marker".
Mỗi tag tương ứng với một version của thư viện. CI/CD pipeline sẽ đảm nhiệm phần build và publish.
Hai cách truyền Version trong GitHub Actions
Trong workflow phía trên, chúng ta có thể truyền version vào lệnh dotnet build bằng cú pháp:
dotnet build -c Release /p:Version=${{ steps.meta.outputs.VERSION }}
GitHub Actions thực tế hỗ trợ hai cách để sử dụng giá trị trong workflow:
- GitHub Expression
- Environment Variable
Cách 1: GitHub Expression
GitHub Expression sử dụng cú pháp ${{ ... }}.
Giá trị sẽ được GitHub thay thế trước khi shell chạy script.
- name: Build
run: dotnet build -c Release /p:Version=${{ steps.meta.outputs.VERSION }}
Ví dụ nếu version là 1.2.0, GitHub sẽ biến lệnh trên thành:
dotnet build -c Release /p:Version=1.2.0
Cách này rất gọn và thường được dùng khi giá trị chỉ xuất hiện một lần.
Cách 2: Environment Variable
Một cách khác là lưu version vào biến môi trường.
- name: Set version
run: echo "VERSION=${{ steps.meta.outputs.VERSION }}" >> $GITHUB_ENV
Sau đó có thể dùng trong các bước tiếp theo:
- name: Build
run: dotnet build -c Release /p:Version=$VERSION
- name: Pack
run: dotnet pack -c Release -o ./artifacts /p:Version=$VERSION
Ở đây $VERSION là biến môi trường của shell.
So sánh hai cách
| Cách | Cú pháp | Ưu điểm | Nhược điểm |
|---|---|---|---|
| GitHub Expression | ${{ steps.meta.outputs.VERSION }} |
Ngắn gọn, rõ ràng | Lặp lại nếu dùng nhiều lần |
| Environment Variable | $VERSION |
Dễ tái sử dụng | Cần thêm bước set biến |
${{ }} → được GitHub Actions xử lý trước khi chạy script
$VAR → được shell xử lý khi script chạy
Nên dùng cách nào?
Nếu giá trị chỉ được dùng một lần, cách đơn giản nhất là dùng GitHub Expression:
${{ steps.meta.outputs.VERSION }}
Nếu giá trị được dùng nhiều lần trong workflow (build, pack, publish), việc lưu vào environment variable sẽ giúp workflow gọn gàng hơn:
$VERSION
Trong các pipeline CI/CD thực tế, nhiều project thường kết hợp cả hai cách:
- Dùng
${{ }}để lấy dữ liệu từ step - Lưu vào
$GITHUB_ENVđể tái sử dụng trong nhiều bước
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
Kết luận
Việc tự phát triển NuGet package mang lại nhiều lợi ích cho developer:
- Tái sử dụng code giữa nhiều project
- Phát hành thư viện một cách chuyên nghiệp
- Tự động hóa build và release
Với GitHub Actions và Git Tag, quy trình phát hành package trở nên rất gọn gàng:
Write Code → Commit → Git Tag → CI/CD → NuGet
Đây là workflow được nhiều thư viện .NET lớn sử dụng.
Hy vọng bài viết này giúp bạn bắt đầu xây dựng thư viện của riêng mình.
Chúc các bạn thành công!
Nhận xét
Đăng nhận xét