This document introduces the Infrastructure Layer in LingFrame's three-tier architecture and its development methods.
┌─────────────────────────────────────────────────────────┐
│ Core (Governance Kernel) │
│ Auth Arbitration · Audit · Scheduling · Isolation │
└────────────────────────────┬────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ Infrastructure (Infra Layer) │ ← This Document
│ Storage · Cache · Message · Search │
└────────────────────────────┬────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ Business Lings (Business Layer) │
│ User Center · Order Service · Payment │
└─────────────────────────────────────────────────────────┘
The Infrastructure Proxy is the Middle Layer of the three-tier architecture, responsible for:
- Encapsulate Bottom Capabilities: Database, Cache, Message Queue, etc.
- Fine-grained Permission Interception: Perform permission checks at the API level.
- Audit Reporting: Report operation records to Core.
- Transparent to Business Units: Business units use infrastructure imperceptibly.
Provides database access capabilities, implementing SQL-level permission control via a proxy chain.
Business Unit calls DataSource.getConnection()
│
▼
┌─────────────────────────────────────┐
│ LingDataSourceProxy │
│ - Wraps original DataSource │
│ - Returns LingConnectionProxy │
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ LingConnectionProxy │
│ - Wraps original Connection │
│ - createStatement() → LingStatementProxy
│ - prepareStatement() → LingPreparedStatementProxy
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ LingStatementProxy │
│ LingPreparedStatementProxy │
│ - Intercept execute/executeQuery/executeUpdate
│ - Parse SQL Type (SELECT/INSERT/UPDATE/DELETE)
│ - Call PermissionService to check permission
│ - Report Audit Log │
└─────────────────────────────────────┘
DataSourceWrapperProcessor: Auto-wrap DataSource via BeanPostProcessor
@Component
public class DataSourceWrapperProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataSource) {
PermissionService permissionService = applicationContext.getBean(PermissionService.class);
return new LingDataSourceProxy((DataSource) bean, permissionService);
}
return bean;
}
}LingPreparedStatementProxy: SQL-level Permission Check
public class LingPreparedStatementProxy implements PreparedStatement {
private void checkPermission() throws SQLException {
// 1. Get Caller ling ID
String callerLingId = LingContextHolder.get();
if (callerLingId == null) return;
// 2. Parse SQL Type
AccessType accessType = parseSqlForAccessType(sql);
// 3. Permission Check
boolean allowed = permissionService.isAllowed(callerLingId, "storage:sql", accessType);
// 4. Audit Report
permissionService.audit(callerLingId, "storage:sql", sql, allowed);
if (!allowed) {
throw new SQLException(new PermissionDeniedException(...));
}
}
@Override
public ResultSet executeQuery() throws SQLException {
checkPermission();
return target.executeQuery();
}
}Supports two parsing strategies:
- Simple Match: Short SQL string matching
- JSqlParser: Complex SQL syntax parsing
private AccessType parseSqlForAccessType(String sql) {
// Simple SQL direct matching
if (isSimpleSql(sql)) {
return fallbackParseSql(sql);
}
// Complex SQL using JSqlParser
Statement statement = CCJSqlParserUtil.parse(sql);
if (statement instanceof Select) return AccessType.READ;
if (statement instanceof Insert || Update || Delete) return AccessType.WRITE;
return AccessType.EXECUTE;
}Provides cache access capabilities, supporting Spring Cache, Caffeine, and Redis, implementing permission control via proxies and interceptors.
Business Unit calls cacheManager.getCache("users")
│
▼
┌─────────────────────────────────────┐
│ LingCacheManagerProxy │
│ - Wraps CacheManager │
│ - Returns LingSpringCacheProxy │
└─────────────────┬───────────────────┘
│
▼
┌─────────────────────────────────────┐
│ LingSpringCacheProxy │
│ - Intercept get/put/evict/clear │
│ - Permission Check + Audit Report │
└─────────────────────────────────────┘
Business Unit calls redisTemplate.opsForValue().set(...)
│
▼
┌─────────────────────────────────────┐
│ RedisPermissionInterceptor │
│ - AOP Intercept RedisTemplate methods│
│ - Infer Operation Type (READ/WRITE) │
│ - Permission Check + Audit Report │
└─────────────────────────────────────┘
RedisPermissionInterceptor: Redis Operation Permission Interception
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String callerLingId = LingContextHolder.get();
String methodName = invocation.getMethod().getName();
// Infer Operation Type
AccessType accessType = inferAccessType(methodName);
// Permission Check
boolean allowed = permissionService.isAllowed(callerLingId, "cache:redis", accessType);
// Audit Report
permissionService.audit(callerLingId, "cache:redis", methodName, allowed);
if (!allowed) {
throw new PermissionDeniedException(...);
}
return invocation.proceed();
}
private AccessType inferAccessType(String methodName) {
if (methodName.startsWith("get") || methodName.startsWith("has")) {
return AccessType.READ;
}
if (methodName.startsWith("set") || methodName.startsWith("delete")) {
return AccessType.WRITE;
}
return AccessType.EXECUTE;
}LingSpringCacheProxy: Spring Cache Common Proxy
@Override
public void put(@NonNull Object key, Object value) {
checkPermission(AccessType.WRITE);
target.put(key, value);
}
@Override
public ValueWrapper get(@NonNull Object key) {
checkPermission(AccessType.READ);
return target.get(key);
}| Type | Capability ID | Description |
|---|---|---|
| Spring Cache | cache:local |
Unified Abstraction Layer |
| Redis | cache:redis |
RedisTemplate Interception |
| Caffeine | cache:local |
Local Cache |
<project>
<parent>
<groupId>com.lingframe</groupId>
<artifactId>lingframe-infrastructure</artifactId>
</parent>
<artifactId>lingframe-infra-xxx</artifactId>
<dependencies>
<dependency>
<groupId>com.lingframe</groupId>
<artifactId>lingframe-api</artifactId>
</dependency>
</dependencies>
</project>The core of an infrastructure proxy is the Proxy Pattern:
public class LingXxxProxy implements XxxInterface {
private final XxxInterface target;
private final PermissionService permissionService;
@Override
public Result doSomething(Args args) {
// 1. Get Caller
String callerLingId = LingContextHolder.get();
// 2. Permission Check
if (!permissionService.isAllowed(callerLingId, "xxx:capability", accessType)) {
throw new PermissionDeniedException(...);
}
// 3. Audit Report
permissionService.audit(callerLingId, "xxx:capability", operation, true);
// 4. Execute Real Operation
return target.doSomething(args);
}
}Use BeanPostProcessor for automatic wrapping:
@Component
public class XxxWrapperProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof XxxInterface) {
return new LingXxxProxy((XxxInterface) bean, permissionService);
}
return bean;
}
}Infrastructure proxies need to define clear capability identifiers (capability):
| ling | Capability ID | Description |
|---|---|---|
| storage | storage:sql |
SQL Execution |
| cache | cache:redis |
Redis Operation |
| cache | cache:local |
Local Cache |
| message | message:send |
Send Message |
| message | message:consume |
Consume Message |
Business units declare required capabilities in ling.yml:
id: my-ling
version: 1.0.0
mainClass: "com.example.MyLing"
governance:
permissions:
- methodPattern: "storage:sql"
permissionId: "READ"
- methodPattern: "cache:redis"
permissionId: "WRITE"┌─────────────────────────────────────────────────────────┐
│ Business Unit │
│ userRepository.findById(id) │
│ │ │
│ │ (Transparent Call) │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ MyBatis / JPA │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ (JDBC)
▼
┌─────────────────────────────────────────────────────────┐
│ Infrastructure Proxy (Storage) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ LingDataSourceProxy → LingConnectionProxy │ │
│ │ → LingPreparedStatementProxy │ │
│ │ │ │
│ │ 1. Get callerLingId │ │
│ │ 2. Parse SQL Type │ │
│ │ 3. Call PermissionService.isAllowed() │ │
│ │ 4. Audit Reporting │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
│ (Permission Query)
▼
┌─────────────────────────────────────────────────────────┐
│ Core │
│ PermissionService.isAllowed(lingId, capability, type)│
│ AuditManager.asyncRecord(...) │
└─────────────────────────────────────────────────────────┘
- Proxy Transparency: Business Lings should not be aware of the proxy's existence.
- Capability ID Standardization: Use
ling:Operationformat. - Fine-grained Control: Intercept at the closest point to the operation.
- Async Audit: Audit should not block business flow.
- Cache Optimization: SQL parsing results can be cached.