Migration Guide: Moving to USL¶
Overview¶
This guide helps teams migrate existing applications to USL from popular frameworks. We cover common migration patterns, code examples, and best practices for a smooth transition.
Target Frameworks: - Django (Python) - Ruby on Rails - Express.js (Node.js) - Spring Boot (Java)
Migration Timeline: 4-12 weeks depending on application size
Table of Contents¶
- Migration Strategy
- Django to USL
- Rails to USL
- Express to USL
- Spring Boot to USL
- Common Patterns
- Testing Strategy
- Deployment
Migration Strategy¶
Recommended Approach¶
Phase 1: Parallel Run (Weeks 1-4) - Keep existing application running - Build new features in USL - Proxy new endpoints to USL service - Gradual traffic shifting
Phase 2: Core Migration (Weeks 5-8) - Migrate database schema - Rewrite core business logic - API compatibility layer - Comprehensive testing
Phase 3: Complete Cutover (Weeks 9-12) - Migrate remaining features - Data migration - Sunset old application - Monitor and optimize
Key Principles¶
- Incremental Migration: Don't rewrite everything at once
- Feature Parity First: Match existing functionality before adding new features
- Test Coverage: Maintain or improve test coverage during migration
- Monitoring: Enhanced monitoring during transition
- Rollback Plan: Always have a rollback strategy
Django to USL¶
Overview¶
Django is a Python web framework with ORM, admin interface, and batteries-included philosophy. USL provides similar capabilities with type safety across the full stack.
Feature Mapping¶
| Django Feature | USL Equivalent | Notes |
|---|---|---|
| Models (ORM) | Entity definitions | Similar syntax, stronger types |
| Views | API handlers | More concise, type-safe |
| URLs | API routing | Automatic from entity definitions |
| Forms | Input validation | Built into API definitions |
| Admin | Auto-generated admin UI | Coming in v1.1 |
| Middleware | Request/response hooks | Similar concept |
| Migrations | USL migrations | Automatic from schema changes |
| Auth | Built-in auth system | More secure by default |
Code Examples¶
Django Model → USL Entity¶
Django:
# models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
USL:
entity Post {
id: uuid primary default(uuid_v4())
title: string validate(length >= 1, length <= 200)
content: text
author_id: uuid foreign(User)
author: User relation(author_id)
published: boolean default(false)
created_at: timestamp default(now())
updated_at: timestamp default(now()) on_update(now())
}
// Automatic ordering by created_at in queries
Django View → USL API¶
Django:
# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from .models import Post
@require_http_methods(["GET"])
def list_posts(request):
published_only = request.GET.get('published', 'true') == 'true'
posts = Post.objects.filter(published=published_only)
return JsonResponse({
'posts': [
{
'id': post.id,
'title': post.title,
'author': post.author.username,
'created_at': post.created_at.isoformat()
}
for post in posts
]
})
USL:
api PostAPI {
GET "/posts" list_posts {
auth: required
query {
published: boolean default(true)
}
handler {
posts = Post.where(published == query.published)
.order_by(created_at, desc)
.include([author])
.all()
return {
status: 200,
data: posts
}
}
}
}
Django Forms → USL Validation¶
Django:
# forms.py
from django import forms
class PostForm(forms.Form):
title = forms.CharField(max_length=200, required=True)
content = forms.CharField(widget=forms.Textarea, required=True)
published = forms.BooleanField(required=False)
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 5:
raise forms.ValidationError("Title must be at least 5 characters")
return title
USL:
api PostAPI {
POST "/posts" create_post {
auth: required
input {
title: string validate(length >= 5, length <= 200)
content: string validate(length >= 10)
published: boolean default(false)
}
handler {
post = Post.create({
...input,
author_id: current_user.id
})
return {
status: 201,
data: post
}
}
}
}
Django Authentication¶
Django:
# views.py
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
@login_required
def protected_view(request):
return JsonResponse({'user': request.user.username})
USL:
api UserAPI {
POST "/auth/login" login {
input {
email: string validate(email_format)
password: string
}
handler {
user = User.where(email == input.email).first()
if !user or !verify_password(input.password, user.password_hash) {
return error(401, "Invalid credentials")
}
token = generate_jwt(user.id)
return {
status: 200,
data: {
token: token,
user: user
}
}
}
}
GET "/protected" protected {
auth: required
handler {
return {
status: 200,
data: {
user: current_user.email
}
}
}
}
}
Migration Steps¶
-
Set Up USL Project
-
Convert Models to Entities
- Start with core models
- Map Django field types to USL types
- Define relationships
-
Add validation rules
-
Migrate Database Schema
-
Convert Views to API Handlers
- One view at a time
- Maintain API contract
- Add type safety
-
Improve error handling
-
Data Migration
-
Testing
- Write USL tests
- Integration tests for API compatibility
- Load testing
-
Security testing
-
Deploy
Rails to USL¶
Feature Mapping¶
| Rails Feature | USL Equivalent | Notes |
|---|---|---|
| ActiveRecord | Entity definitions | Similar conventions |
| Controllers | API handlers | RESTful by default |
| Routes | Automatic routing | Convention over configuration |
| Validations | Built-in validators | Type-safe |
| Migrations | Auto migrations | From schema |
| ActionMailer | Email service | Built-in |
| ActiveJob | Background jobs | Built-in |
| Asset Pipeline | Frontend generation | TypeScript client |
Code Examples¶
Rails Model → USL Entity¶
Rails:
# app/models/user.rb
class User < ApplicationRecord
has_many :posts
has_secure_password
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
scope :active, -> { where(active: true) }
def full_name
"#{first_name} #{last_name}"
end
end
USL:
entity User {
id: uuid primary default(uuid_v4())
email: string unique validate(email_format) indexed
password_hash: encrypted<string>
first_name: string validate(length >= 2, length <= 100)
last_name: string validate(length >= 2, length <= 100)
active: boolean default(true) indexed
created_at: timestamp default(now())
updated_at: timestamp default(now()) on_update(now())
posts: [Post] relation(user_id)
// Computed field
full_name: string computed(first_name + " " + last_name)
}
// Scope equivalent
query active_users {
User.where(active == true)
}
Rails Controller → USL API¶
Rails:
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :set_post, only: [:show, :update, :destroy]
def index
@posts = Post.where(published: true).order(created_at: :desc)
render json: @posts, include: [:author]
end
def create
@post = current_user.posts.build(post_params)
if @post.save
render json: @post, status: :created
else
render json: @post.errors, status: :unprocessable_entity
end
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :content, :published)
end
end
USL:
api PostAPI {
GET "/posts" index {
auth: required
handler {
posts = Post.where(published == true)
.order_by(created_at, desc)
.include([author])
.all()
return {
status: 200,
data: posts
}
}
}
POST "/posts" create {
auth: required
input {
title: string validate(length >= 1, length <= 200)
content: text
published: boolean default(false)
}
handler {
post = Post.create({
...input,
user_id: current_user.id
})
return {
status: 201,
data: post
}
}
}
GET "/posts/:id" show {
auth: required
handler {
post = Post.find(params.id)
if !post {
return error(404, "Post not found")
}
return {
status: 200,
data: post
}
}
}
}
Migration Steps¶
-
Install USL
-
Convert Models
-
Convert Controllers
-
Migrate Data
-
Test & Deploy
Express to USL¶
Feature Mapping¶
| Express Feature | USL Equivalent |
|---|---|
| Routes | API handlers |
| Middleware | Request/response hooks |
| mongoose/TypeORM | Entity definitions |
| Joi/Yup validation | Built-in validation |
| Passport.js | Built-in auth |
| Express-validator | Type-safe validation |
Code Examples¶
Express Route → USL API¶
Express:
// routes/posts.js
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
const auth = require('../middleware/auth');
router.get('/posts', auth, async (req, res) => {
try {
const posts = await Post.find({ published: true })
.populate('author')
.sort({ createdAt: -1 });
res.json({ posts });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/posts', auth, async (req, res) => {
try {
const { title, content } = req.body;
if (!title || title.length < 5) {
return res.status(400).json({ error: 'Title must be at least 5 characters' });
}
const post = new Post({
title,
content,
author: req.user.id
});
await post.save();
res.status(201).json({ post });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
USL:
api PostAPI {
GET "/posts" list {
auth: required
handler {
posts = Post.where(published == true)
.order_by(created_at, desc)
.include([author])
.all()
return {
status: 200,
data: { posts }
}
}
}
POST "/posts" create {
auth: required
input {
title: string validate(length >= 5)
content: text
}
handler {
post = Post.create({
...input,
author_id: current_user.id
})
return {
status: 201,
data: { post }
}
}
}
}
Migration Steps¶
-
Create USL Project
-
Convert Models
- Mongoose schemas → USL entities
- Add type safety
-
Improve validation
-
Convert Routes
- Express routes → USL API handlers
- Remove boilerplate
-
Add automatic validation
-
Migrate Middleware
- Auth middleware → USL auth system
-
Custom middleware → USL hooks
-
Test & Deploy
Spring Boot to USL¶
Feature Mapping¶
| Spring Boot Feature | USL Equivalent |
|---|---|
| JPA Entities | Entity definitions |
| RestControllers | API handlers |
| Spring Security | Built-in security |
| Bean Validation | Type-safe validation |
| Spring Data | Query system |
| @Transactional | Built-in transactions |
Code Examples¶
Spring Entity → USL Entity¶
Spring Boot:
@Entity
@Table(name = "posts")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false, length = 200)
private String title;
@Column(columnDefinition = "TEXT")
private String content;
@ManyToOne
@JoinColumn(name = "author_id")
private User author;
private Boolean published = false;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
USL:
entity Post {
id: uuid primary default(uuid_v4())
title: string validate(length >= 1, length <= 200)
content: text
author_id: uuid foreign(User)
author: User relation(author_id)
published: boolean default(false)
created_at: timestamp default(now())
updated_at: timestamp default(now()) on_update(now())
}
Common Patterns¶
Authentication¶
Pattern: JWT-based authentication
Before (Generic):
1. User submits credentials
2. Server validates against database
3. Generate JWT token
4. Return token to client
5. Client sends token in Authorization header
6. Server validates token on each request
USL:
api AuthAPI {
POST "/auth/login" login {
input {
email: string validate(email_format)
password: string
}
handler {
user = User.where(email == input.email).first()
if !user or !verify_password(input.password, user.password_hash) {
return error(401, "Invalid credentials")
}
token = generate_jwt(user.id, expires_in: 24 hours)
return {
status: 200,
data: { token, user }
}
}
}
}
// All endpoints with auth: required automatically validate JWT
Pagination¶
USL Pattern:
api PostAPI {
GET "/posts" list {
query {
page: int default(1) validate(>= 1)
per_page: int default(20) validate(>= 1, <= 100)
}
handler {
offset = (query.page - 1) * query.per_page
posts = Post.query()
.limit(query.per_page)
.offset(offset)
.all()
total = Post.count()
return {
status: 200,
data: posts,
meta: {
page: query.page,
per_page: query.per_page,
total: total,
total_pages: ceil(total / query.per_page)
}
}
}
}
}
File Uploads¶
USL Pattern:
api FileAPI {
POST "/upload" upload_file {
auth: required
input {
file: file validate(
max_size: 10MB,
allowed_types: [image/jpeg, image/png, application/pdf]
)
title: string? optional
}
handler {
// Automatic virus scanning
if !scan_file(input.file) {
return error(400, "File failed security scan")
}
// Upload to storage (S3, etc.)
url = storage.upload(input.file, {
path: "uploads/${current_user.id}/${input.file.name}",
encryption: true
})
// Save metadata
file = File.create({
user_id: current_user.id,
title: input.title || input.file.name,
url: url,
size: input.file.size,
mime_type: input.file.type
})
return {
status: 201,
data: file
}
}
}
}
Testing Strategy¶
Unit Tests¶
USL Test Syntax:
test "Create post with valid data" {
given {
user = create_test_user()
post_data = {
title: "Test Post",
content: "This is a test",
published: false
}
}
when {
response = api.post("/posts", post_data, auth: user)
}
then {
assert response.status == 201
assert response.data.title == "Test Post"
assert response.data.author_id == user.id
}
}
Migration Testing¶
- Parallel Run Testing
- Run both old and new systems
- Compare responses
-
Identify discrepancies
-
Data Integrity Testing
- Verify all data migrated
- Check relationships preserved
-
Validate no data loss
-
Performance Testing
- Load testing
- Compare response times
- Identify bottlenecks
Deployment¶
Zero-Downtime Migration¶
- Phase 1: Deploy USL alongside existing app
- Phase 2: Route new traffic to USL
- Phase 3: Gradually shift existing traffic
- Phase 4: Monitor and optimize
- Phase 5: Sunset old application
Rollback Strategy¶
Always maintain ability to rollback:
# Rollback plan
1. Keep old application running
2. Database compatible with both versions
3. Feature flags for gradual rollout
4. Monitoring for issues
5. One-command rollback procedure
Support¶
Need help with migration? - Discord: #migration-help - Email: migration-support@usl.dev - Docs: https://usl.dev/migration - Office Hours: Tuesday 2-3 PM ET
Last Updated: January 15, 2026