Designing a Lightweight Domain‑Driven Layered Architecture for Java Backend
This article presents a lightweight, domain‑model‑based layered architecture for Java backend development, detailing six layers (web, biz, dal, client, common, facade), their package structures, call relationships, layer conventions, and providing reusable code snippets and utilities to guide clean and maintainable application design.
Preface In Java development, naming and layering are common pain points; this article proposes a lightweight domain‑model‑based layered architecture.
1. Layer Structure
The application is divided into six layers:
web (frontend request layer) : handles incoming requests from the web front‑end.
biz (business layer) : provides encapsulated capabilities and orchestrates business logic.
dal (data access layer) : performs CRUD operations on underlying data sources.
client (external request layer) : defines interfaces exposed to other applications.
common (external public layer) : contains public classes exposed externally.
facade (facade layer) : forwards external calls to the business layer.
2. Layer Details
web packages include controller, model (entity, request, vo), and convert (controller‑to‑service and dto‑to‑vo).
biz packages include service (query and domain services), ability (domain capabilities), manager (generic logic for data models), remote (external services), message (producer), diamond (dynamic config), tair (cache), and config (business configuration).
common packages include constants, enums, internal utilities, convert (dto‑do and service‑to‑manager converters), model.dto, model.request, model.option, and utils.
dal packages include mapper, adb, tddl, and model (dataobject, query, config).
client package includes api (HSF interfaces).
facade packages include api.impl, convert, listener, and consumer.
3. Call Relationships
Key points: services can call each other directly; a service may invoke multiple domain abilities; domain abilities are minimal, non‑callable units; query services call managers directly, not domain abilities.
4. Layer Conventions
web : unified exception‑handling aspect.
biz : internal services do not handle exceptions or wrap results; exceptions are propagated to web and facade layers; separate query services; each ability corresponds to a single domain.
dal : mappers are separated by data source (adb, xdb).
common : only expose external entities, constants, enums; DTOs contain only necessary fields.
facade : unified exception‑handling aspect; HSF implementation classes perform only parameter validation and conversion.
5. Common Code and Tools
Unified exception handling:
@RestControllerAdvice
public class RestExceptionHandler {
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(Exception.class)
public Result system(HttpServletRequest req, Exception e) {
AllLoggers.EXCEPTION.error("RestExceptionHandler.system|servlet:{}|method:{}|code:{}|msg:{}",
req.getServletPath(), req.getMethod(), e.getMessage(), e);
return Result.error(ResultCode.BASE.SYSTEM_ERROR);
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(BusinessException.class)
public Result business(HttpServletRequest req, BusinessException e) {
AllLoggers.EXCEPTION.error("RestExceptionHandler.business|servlet:{}|method:{}|code:{}|msg:{}",
req.getServletPath(), req.getMethod(), e.getMessage(), e);
return Result.error(e.getErrorCode(), e.getErrorMessage());
}
}Logging utilities:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public interface AllLoggers {
Logger APPLICATION = LoggerFactory.getLogger("APPLICATION");
Logger EXCEPTION = LoggerFactory.getLogger("EXCEPTION");
Logger BIZ = LoggerFactory.getLogger("BIZ");
Logger HSF = LoggerFactory.getLogger("HSF");
Logger MTOP = LoggerFactory.getLogger("MTOP");
}Logback configuration (excerpt):
<configuration>
<property name="APP_NAME" value="toms"/>
<property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs"/>
<appender name="APPLICATION" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/toms-root.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/logs_saved/toms-root.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>5</maxHistory>
<maxFileSize>1GB</maxFileSize>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
</appender>
...
</configuration>Unit conversion utilities:
public class UnitConvertUtils {
public static final double RATE_OF_METRE_AND_KILOMETRE = 1000d;
public static final int INT_RATE_OF_METRE_AND_KILOMETRE = 1000;
public static final double RATE_OF_FEN_AND_YUAN = 100d;
public static final double INT_RATE_OF_CM3_AND_M3 = 1000000d;
public static Double convertMetre2Kilometre(Long toConvert) {
if (toConvert == null) return null;
return toConvert / RATE_OF_METRE_AND_KILOMETRE;
}
public static Long convertKilometre2Metre(Double toConvert) {
if (toConvert == null) return null;
return BigDecimal.valueOf(toConvert).multiply(BigDecimal.valueOf(RATE_OF_METRE_AND_KILOMETRE)).longValue();
}
// Additional conversion methods omitted for brevity
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
