UnilearningUnified online learning platform
An online learning SaaS for enterprises, institutions and individuals, rebuilt as a Go modular monolith: 8 business domains (auth / cms / eas / ocs / oms / osm / quiz / ai) aggregated in-process, folding 6 microservices into 1 binary and 1 container, with domains communicating only through api contracts and the internal wall enforced by the compiler.
Once microservices are split finely enough, the real cost is inter-service calls. A modular monolith turns those calls back into function calls while using the compiler to guard domain boundaries.
From course publishing to AI-assisted grading, every step of the teaching loop is a business domain that can evolve independently.
What problem does it actually solve
Four core capabilities, mapped to four real business needs.
Courses, courseware, scheduling, assignments, exams and comments cover the full teaching and learning flow, with progress and grades queryable in real time.
Eight business domains aggregated in-process; domains recognise only api contracts, and the internal wall is enforced by the compiler — not left to lint discipline.
Teachers trigger AI grading for one submission or a whole class; drafts land in extension attributes and never touch business state. The final call always belongs to a person.
Versioned SQL migrations upgrade idempotently, legacy data imports in one pass, and existing users log in with their original passwords.
Capabilities at a glance
- Course tree and learning records: a materialised path tree where progress bubbles up the ancestor chain automatically
- Four courseware types — content, page, video, media — with unified progress tracking and resume
- Question bank and exams: single choice, multiple choice and true/false papers, scored and explained on submission
- AI first-pass grading: the LLM drafts, the teacher has the final say — AI never decides for the teacher
- Online store: courses as purchasable goods, with the order as proof of purchase
- Academic scheduling: classes, members and timetables, with three front-end routes compatible with the legacy system
Not a product that lives in slides
The numbers below come from the real code in the unile-go repository: modules, tables, endpoints and capability items.
Tech stack
Go deeper
Each subpage answers a specific kind of question — not the same content re-typeset.
Features
8 business domains, 48 concrete capabilities — each one backed by an implementation that exists in the code.
Course tree & learning records
cms 域The outline is a tree: finish one section and progress along the whole ancestor chain updates automatically.
- Materialised path tree: the outline is stored as path encodings, so subtree queries need no recursion
- Learning records upsert along the ancestor chain, aggregating parent progress automatically
- Node types are extensible; a paper can hang off the outline with node_type=quiz
- RecordService.UpdateAttributes merges into extension columns, keeping AI drafts out of business fields
- Progress and grades are queryable in real time; learner and teacher views share one source of truth
Four courseware types
ocs 域Rich text, pages, video and media — one progress model for all.
- content: rich-text courseware
- page: custom-page courseware
- video: video courseware with resume support
- media: audio, video and other media forms
- A unified courseware progress record model
Academic scheduling
eas 域Classes, members and timetables — the daily work of academic affairs.
- User profiles: learners and teachers managed uniformly
- Class management: class CRUD and memberships
- Member roles: teachers, teaching assistants and learners
- Scheduling: the schedules table links classes to outline nodes
- expand parameter compatibility: teachers / assistants / students fetched in one call
Question bank & exams
quiz 域Objective questions are scored on the spot; subjective ones are left to the teacher.
- Question banks isolated per course, with single-choice, multiple-choice and true–false questions
- Options and answers stored in JSONB; option indices start at 0
- Whole-paper assembly: quiz_paper_questions links papers to questions
- Scored on submission: a pure set-comparison function runs server-side automatically
- Explanations on submission: the response returns per-question correctness, correct answers and analysis
- Answer-stripped retrieval: the learner paper endpoint never returns answer / analysis
- Multiple attempts: attempts keep history; progress takes the latest, score takes the highest
- Scoring and progress decoupled: a progress failure does not roll back scoring — it flags sync_record_failed
AI-assisted grading
ai 域The AI drafts; the teacher decides — that boundary is written into the implementation.
- Single submission: POST /tv1/teacher/homeworks/:hid/ai-feedback
- Whole class: POST /tv1/teacher/classes/:cid/ai-feedback, chunked with a cap of 100 per batch
- Only submitted work is processed; items with existing drafts are skipped — no burning tokens twice
- A single failure does not abort the batch; errors are reported per item and retried automatically next time
- Drafts are written to record.attributes.ai_feedback, never touching business state columns
- Final review still goes through the existing feedback endpoint — AI never decides for the teacher
- The LLM client implements the OpenAI-compatible protocol on the standard library alone; GLM / DeepSeek / local Ollama all work
- Unconfigured, the endpoint returns 501 and nothing else is affected
Comments & community
oms 域Threaded comments, with the whole subtree fetched in one query.
- Threaded tree: root / path / depth carry the hierarchy
- Subtree queries: one query retrieves an entire subtree
- Comments attach to courses and nodes
- Ported from the legacy system, with unit tests covering the core algorithm
Online store
osm 域Courses become goods; the order is the proof of purchase.
- Product management: course-snapshot products, prices in cents
- Product state machine: draft → on_sale ⇄ off_sale
- The learner storefront shows only on_sale products
- Ordering: the product snapshot is persisted; the order is the receipt
- Order state machine: pending → paid | cancelled, paid → refunded
- Admins can confirm offline payment (MarkPaid) and issue refunds
- Orders have no delete endpoint — they are retained as financial records
Identity & migration
auth 域 · serverSwap the engine without changing the tyres: legacy data works as-is.
- local mode: bcrypt + HS256 JWT built in, ready out of the box
- remote mode: switch to UIAM without touching edge or any domain code
- Versioned SQL migrations: one set of pg / mysql dialect files per domain, applied idempotently at startup
- Migration versions recorded in the in-database unile_migrations table; applied ones are skipped automatically
- The legacy-import tool loads the old database in one pass; existing users log in with their original bcrypt passwords
- An OSS STS credential endpoint supporting both real AssumeRole and static modes
API contract
12 representative endpoints. What is listed here is the skeleton of the public contract; full definitions ship with the deployment.
/uv1/auth/loginLearner login: local bcrypt, or remote mode delegating to UIAM
/uv1/courses/{id}/outlineCourse tree outline; the materialised path fetches an entire subtree in one call
/uv1/recordsSubmits a learning record; progress bubbles automatically along the ancestor chain
/uv1/nodes/{id}/paperFetch a paper: the server strips answer / analysis before returning
/uv1/nodes/{id}/attemptsSubmit an attempt: scored in one transaction with per-question analysis returned
/tv1/teacher/homeworks/{hid}/ai-feedbackAI grading for one submission; drafts written to the attributes extension column
/tv1/teacher/classes/{cid}/ai-feedbackWhole-class AI grading, chunked, up to 100 per batch
/tv1/teacher/homeworks/{hid}/feedbackThe teacher’s final review — the decision always stays human
/admv1/shop/productsAdmin product management with the draft → on_sale ⇄ off_sale state machine
/uv1/ordersPlace an order: the product snapshot persists; the order is the proof of purchase
/admv1/orders/{id}/mark-paidOffline payment confirmation, advancing the order state machine
/uv1/nodes/{id}/commentsThreaded comments on the root / path / depth tree
One real call
What an endpoint actually looks like says more than a list of endpoints.
01$ curl -X POST https://learn.zhenbei.tech/uv1/nodes/146/attempts \02 -d '{"answers":[{"qid":9,"choice":[0,2]},{"qid":10,"choice":[1]}]}'0304{ "score": 50, "total": 100, "graded": "instant",05 "detail": [ { "qid": 9, "ok": false, "answer": [0,1], "analysis": "…" },06 { "qid": 10, "ok": true } ],07 "progress": { "node": 146, "bubbled": 12 } }Contract discipline
An endpoint is a promise, not an exposure of implementation details. These three rules are ones we always keep.
Breaking changes go through a major version, announced one release cycle ahead. A shipped endpoint never changes semantics because of internal refactoring.
Error codes carry semantics instead of a blanket 500, so callers — including AI agents — can decide whether to retry or give up.
Structured data is returned by default instead of human-readable prose, so AI can read it directly — no human screenshotting and relaying.
Modules
5 groups, 11 code modules — all from the real directory structure of the unile-go repository.
server/ + per-domain modulesWhat is taught, what is learned, and where a learner is.
cmsCourse tree (materialised path) and learning records; progress upserts bubble along the ancestor chain
cms/internal/bizocsFour courseware types: content / page / video / media, plus courseware progress
ocs/internal/bizWho teaches, who learns, and when.
easAcademic affairs: full coverage of users, classes, class_user_relations and schedules
eas/internal/bizLearning must be verifiable.
quizQuestion bank (single/multiple choice, true–false; options/answer in JSONB) + paper assembly + scoring
quiz/internal/bizaiLLM grading: the OpenAI-compatible protocol on the standard library alone, zero new dependencies
ai/internal/bizLearning is not solitary, and courses can be sold.
omsThreaded comments: root / path / depth tree + subtree queries
oms/internal/bizosmOnline store: product listings, orders, payment, refunds and order management
osm/internal/bizBoundaries guarded by the compiler, not by documents.
authIdentity: local bcrypt + HS256 JWT built in, smoothly switchable to remote for UIAM
auth/internal/bizedgeAggregation layer: uv1 / tv1 / admv1 routes, paths kept compatible with the legacy front end
edge/baseShared types: pagination, the attribute extension column, unified error codes — zero external dependencies
base/serverAssembly layer: configuration, the migration runner, dependency injection and startup
server/Layered design
From access to runtime — what each layer owns and what it is built with.
server: configuration, migrations, dependency injection and startup; delivered as a single binary via Dockerfile
edge: uv1 learner / tv1 teacher / admv1 admin — cross-domain orchestration is written only here
cms course tree and learning records · ocs four courseware types and progress · eas academic scheduling
oms threaded comments · osm course store · quiz question bank and exams · ai LLM grading
auth: local bcrypt + HS256 JWT built in, switchable to remote for UIAM
base: pagination, attributes, error codes — zero external dependencies
PostgreSQL / MySQL dual dialects, with per-domain versioned migrations applied automatically at startup
Key flows
The most important paths, unpacked step by step.
A learner finishes a lesson
One submission updates progress across the whole outline tree.
- 1
The learner submits a progress record for a node on uv1
- 2
cms validates node ownership and course access
- 3
The record upserts into the learning-record table
- 4
Progress bubbles up the materialised-path ancestor chain
- 5
Parent nodes and course-level progress refresh in step
- 6
The new progress is visible at once in both the learner outline view and the teacher progress view
Online exam scored on submission
Objective questions never wait for a teacher.
- 1
The learner requests a node with a quiz attached; the server strips answers and analysis before returning
- 2
The learner submits answers to /uv1/.../attempts
- 3
edge verifies the node really has a paper attached and hands over to the quiz domain
- 4
The quiz domain scores each question with a pure set-comparison function (unanswered = wrong, 0 points)
- 5
The attempt and per-question details persist in one transaction, with total snapshotted at submission
- 6
cms Records.Save writes the score and completion state; progress bubbles along the ancestor chain
- 7
The response returns the full scoring detail: per-question correctness, correct answers and analysis
AI grading and teacher review
The AI speeds things up; the decision stays human.
- 1
A learner submits homework with status submitted
- 2
The teacher triggers AI grading for one submission or the whole class from the homework view
- 3
edge assembles context: course, node, assignment requirements and courseware titles
- 4
The ai domain calls the LLM for grading suggestions (skipping items that already have drafts)
- 5
Drafts are written to record.attributes.ai_feedback; business state columns are untouched
- 6
The teacher sees the AI draft in the homework view
- 7
The teacher gives the final verdict through the existing feedback endpoint; the draft stays in the extension column for reference
Tech stack
Scale & benchmarks
These numbers are not estimates — they are read from the code and runtime configuration. Each one states what it actually means, so it cannot be mistaken for marketing.
unile-goauth / cms / eas / ocs / oms / osm / quiz / ai aggregated in-process
Six microservices folded into one binary and one container
Domains recognise only api contracts; the internal wall is compiler-enforced
PostgreSQL first, MySQL equally supported; migrations maintained per dialect
Passing on both databases, covering the main teaching paths
Including 121 courseware items, 10 classes and 40 users, with all IDs preserved
Facts you can count in the repository
Every item below can be checked in unile-go: modules, tables, endpoints and capability items. Whether something is “done” is judged by whether these numbers moved.
Security and compliance support points
What Unilearning concretely does for security, and where the boundaries are drawn. Every item notes where it is implemented, so you can verify.
The learner paper endpoint never returns answer / analysis — correct answers are never sent to the client.
quiz domain paper endpointCross-domain code may import only the other domain’s api contract; violations fail to compile rather than violating a document.
Go internal mechanismlocal mode ships bcrypt + JWT built in; switching to remote connects UIAM with no changes to other domains.
auth domain local / remoteOne set of dialect migration files per domain, versions recorded in-database, applied versions skipped, failures reversible.
the unile_migrations tableThe OSS STS credential endpoint supports real AssumeRole, avoiding hard-coded long-term keys.
the OSS STS endpointAll three route families (uv1 / tv1 / admv1) now require authentication; some legacy routes had none.
the edge aggregation layerWhere the data stops
The data boundary is determined by the product form, not a toggle someone can flip. This is the shared judgement across all four business lines.
The minimum boundary for endpoint products. Data at this layer has no upload path by design — it is not switched off by a toggle.
- All legdger ledger data: encrypted locally, only ciphertext reaches the cloud
- legdger on-device AI statistics and Q&A: inference runs on the device
- NewTool desktop and wasm forms: the algorithm kernel has no IO, so data never leaves the process
- pxc captured traffic: the kernel runs locally and does not pass through third-party services
The boundary for private deployment. Models, vectors, business data and audit records are all deployed inside the customer’s own network.
- Uniclaw memory and knowledge vectors: self-hosted Milvus, never leaving the intranet
- Uniclaw model services: can connect to privately deployed compatible-protocol services
- UIAM identity and audit data: the whole deployment sits inside the customer boundary and can run fully air-gapped
- Uniscrm assets and media: object storage can connect to the customer’s own OSS
- Unilearning courseware and learning records: delivered as a single container, with data under the customer’s control
The only things needing external network access are sync and external channels, and what travels is ciphertext or already-desensitised messages.
- legdger cross-device sync: uploads only the latest ciphertext, and the server keeps no history
- Uniscrm WeCom channel: communicates with official WeCom interfaces and uses official archiving capability
- Uniclaw channels: Feishu / DingTalk push APIs, with content isolated per tenant
- NewTool remote calls: transported over MCP, while the algorithm kernel itself makes no network requests
Company-wide security principles
Whichever business line, these six are the shared floor.
Any constraint that can be baked into the compiler, the framework or the query layer does not go into a document for people to remember. Tenant isolation is enforced by a query-layer listener; domain boundaries are rejected at compile time by Go’s internal mechanism.
jOOQ TenantScopeVisitListener · Go internal 墙Sensitive data stays on the user device or inside the customer network by default. All four business lines support private deployment, and legdger does not even send AI analysis off the device.
legdger 端侧 AI · Uniclaw 自建 Milvus · Unilearning 单容器Agents never share human credentials, servers never hard-code long-lived keys, and endpoint integrations use revocable tokens. Any credential can be revoked on its own without affecting other principals.
UIAM MACHINE 主体 · OSS STS AssumeRole · ledger-cli 令牌Authentication, authorization, tool calls and captured traffic all produce structured records. We keep them not for compliance theatre but so that incidents can be replayed.
uiam-audit · collaboration-logger · pxc 会话记录Delegation is expressed with the act claim — who acts for whom — with permissions intersected at every hop. Audit can answer which user authorized which agent, which tool it called, and with which permissions.
RFC 8693 Token Exchange · agent_tools 注册表Database changes go through versioned migrations that produce the same result on repeat; application releases are single-image swaps, so rollback means switching back to the previous image.
Liquibase · 各域方言迁移 · Docker 单镜像Deploy & integrate
Every product line supports private deployment; the concrete form varies by product.
1 binary, 1 container — a dozen containers become 1.
- make docker && docker run -p 8080:8080
- GOWORK=off resolved via the replace directives in each go.mod
- Per-domain versioned migrations run automatically at startup
PostgreSQL first, MySQL equally supported.
- Pick one driver in config.yaml
- Migrations maintained in pg / mysql directories
- 17 end-to-end smoke cases pass on both databases
A smooth switch from 6 old microservices, with no front-end changes.
- uv1 / tv1 / admv1 route paths stay front-end compatible
- legacy-import loads the old database with all IDs preserved
- Existing users log in directly with their original bcrypt passwords
Runs standalone, or hand identity to UIAM.
- local mode: built-in bcrypt + JWT, zero external dependencies
- remote mode: replace the auth implementation to connect UIAM — no other domain changes
- Authentication now covers all three route families (some legacy routes had none)
Comparison
The same job, done in different ways. The left column is our choice; the right is the common alternative — the difference is usually not in the feature table but in where the boundary is drawn.
Who feels the difference
Feature lists do not convince people; role perspectives do. Below are the real before-and-after differences for four roles.
Training finishes with no answer to who actually completed it, or where people got stuck
Progress aggregates automatically along the outline tree; completion and sticking points are queryable in real time
Dozens of submissions per class; subjective questions keep teachers grading late into the night, and some slip through
The AI drafts the grading; teachers only give the final verdict, cutting repetitive work sharply
A video stops halfway and the position is lost; progress never matches up
Four courseware types share one progress model, resume works, and the outline view stays in sync
A swarm of microservice containers; one change means a rolling upgrade of the whole cluster
Single-container delivery with automatic idempotent migrations at startup; rollback is swapping back the previous image
Releases and roadmap
Shipped items state what was delivered, in-progress items what is being built, planned items what is intended. Shipped items are never reversed.
v1.0ReleasedTeaching core- Eight business domains as a modular monolith
- Course tree with bubbling learning records
- Four courseware types with resume
- Question-bank papers scored on submission
v1.1ReleasedAI & commerce- Single and whole-class AI grading
- The online store and order state machine
- A threaded comment community
- Legacy import of the old system
v1.3In developmentLearning experience- Learning-path recommendations
- Certificates and a credit system
- Live-stream courseware
- Offline learning on mobile
v2.0PlannedMulti-tenancy- Multi-institution tenant isolation
- An open API and plugin ecosystem
- Learning analytics dashboards
- Full federation with UIAM
Integrations and counterparts
Who Unilearning needs to integrate with, and how.
The preferred dialect; migrations maintained in the pg directory, all 17 smoke cases passing
Equally supported, with business queries kept dual-dialect compatible
Implemented on the standard library alone; GLM / DeepSeek / local Ollama all work
Switching the auth implementation to remote completes the integration; no other domain changes
Courseware and media, with real AssumeRole temporary credentials
One-pass import of the old content and identity databases, with IDs kept and passwords working
Adoption scenarios
How this is used in real business settings.
Onboarding, compliance exams and role certification on one chain.
- The course tree organises the training outline
- Question-bank exams handle compliance assessment
- Progress dashboards track completion
Courses to sell, learners to manage, homework to grade.
- The osm store closes the course-sales loop
- Classes and scheduling manage offline and online cohorts
- AI grading cuts repetitive teacher workload
A course business one person can run.
- Single-container deployment with minimal ops cost
- Store and orders work out of the box
- A comment community keeps learners engaged
An old platform already running, without a rewrite.
- Front-end route paths stay compatible
- Legacy data imports in one pass; original passwords keep working
- Migration proceeds domain by domain, keeping risk contained
FAQ
The legacy system split into 6 microservices calling each other over HTTP with URL-based configuration; the real cost sat in inter-service calls and deployment complexity. A modular monolith turns those calls back into in-process function calls, while Go’s internal mechanism makes any cross-domain import fail to compile — boundaries guarded by the compiler, not by lint discipline.
Hand the complexity of identity, agents and private domain to one governable kernel
Whether you are replacing an existing IAM, building an agent platform, or trying to make private-domain operations actually work — start with a 30-minute architecture call. We will first judge whether this is the kind of problem we are good at, and say so plainly if it is not.