软件设计中的DDD模式讲解
本篇文章带大家来系统地讲解一下软件设计中的 DDD(领域驱动设计)模式。
首先,DDD 不是一种具体的技术框架,而是一套软件开发的思想、方法论和模式集合,它的核心目的是解决复杂业务软件的设计与实现脱节的问题,让软件能够真实地反映业务逻辑,并随着业务的发展而演进。
在文章的最后会给出基于SpringBoot框架的DDD模式落地方案。DDD项目模板链接如下,其中包含代码生成器:
https://gitee.com/wang_changlian/ddd-demo
干货分享,感谢阅读
1. DDD 的核心思想与价值
- 核心思想:让业务专家和开发人员使用统一的语言(Ubiquitous Language)来共同构建一个领域的抽象模型,并将这个模型作为软件设计的核心。
- 核心价值:
- 应对复杂性:通过分解和抽象,将庞大的复杂系统拆分为更小、更易管理的部分。
- 统一语言:消除业务人员和开发人员之间的沟通歧义,让代码成为业务模型最直接的体现。
- 可维护性与可扩展性:设计出的软件结构清晰,业务逻辑高内聚、低耦合,易于修改和扩展。
- 清晰的边界:通过界定子域和限界上下文,明确每个模块的职责和边界,避免“大泥球”式的架构。
2. DDD 的两大支柱:战略设计 & 战术设计
DDD 可以大致分为两个层面:
支柱一:战略设计
战略设计关注的是宏观层面的架构和职责划分,它帮助我们理解业务的整体结构,并决定如何切割系统。
核心概念:
-
领域与子域
- 领域:一个组织所从事的业务范围以及其中包含的所有活动。例如,“电商领域”包含商品、订单、支付、物流等。
- 子域:将庞大的领域分解为更小的、更专业的部分。DDD 将子域分为三类:
- 核心域:业务的核心竞争力,是公司最与众不同、最具价值的部分。应将最好的资源和最多的设计投入于此。例如,对于淘宝,其核心域是“商品交易平台”;对于滴滴,是“实时智能调度”。
- 支撑子域:不是核心,但业务必不可少,且需要定制化开发。它支持核心域的运作。例如,电商中的“物流追踪系统”。
- 通用子域:非常常见、通用的功能,通常可以直接购买或使用现成的解决方案。例如,“用户身份认证”、“通知系统”。
-
限界上下文
- 这是 DDD 中最重要、最核心的战略模式。
- 定义:一个显式的边界,领域模型在这个边界内适用。在这个边界内,通用语言的所有术语和短语都有其特定的含义。
- 作用:它定义了模型的适用范围,避免了“一个模型走天下”带来的概念混淆。
- 经典例子:在电商系统中:
- 商品上下文:
Product实体拥有丰富的属性,如分类、SKU、描述、图片等。 - 订单上下文:
OrderItem可能只包含商品ID、快照名称、快照价格。此时它关心的不是商品的详细信息,而是下单那个瞬间的商品状态。 - 虽然都叫“商品”,但在不同的限界上下文中,它们的含义和属性完全不同。
- 商品上下文:
-
上下文映射
- 描述了不同的限界上下文之间如何集成和通信。
- 常见的映射关系:
- 合作关系:两个团队/上下文相互依赖,协同完成一个功能。
- 客户-供应方:上游上下文为下游上下文提供服务。
- 遵奉者:下游Context无条件地遵循上游Context的模型。
- 防腐层:一种非常重要的模式。当需要与一个设计拙劣的外部系统或遗留系统集成时,通过一个中间层(防腐层)来转换和隔离,防止外部系统的“腐败”模型侵蚀自己的核心域模型。
- 开放主机服务:通过定义一套明确的协议(如 RESTful API)来向外提供访问。
- 发布语言:结合开放主机服务,使用一个公共的、文档化的数据交换格式。
支柱二:战术设计
战术设计关注的是微观层面,即在每个限界上下文内部,如何通过一系列的设计模式来构建一个丰富、有效的领域模型。
核心构建块:
-
实体
- 具有唯一标识和生命周期的对象。它的相等性由标识符决定,而不是属性。
- 例子:
User(用户ID)、Order(订单ID)。即使一个用户改了名字,只要ID不变,他依然是同一个实体。
-
值对象
- 没有唯一标识,仅通过其属性值来识别的对象。它们应该是不可变的。
- 例子:
Money(包含金额和货币单位)、Address(包含省、市、街道)。两个金额都是 100 USD 的钱对象是相等的,可以互换。
-
聚合
- 这是战术设计中最重要的模式。它是一组相关实体和值对象的集合,并有一个聚合根作为对外界的唯一访问点。
- 聚合根:是聚合的入口,负责维护聚合内部的业务规则和不变量。
- 例子:
Order(聚合根)和它的OrderItem(实体)。你不能直接操作OrderItem,必须通过Order聚合根来添加或删除。这保证了“订单总价必须等于所有订单项价格之和”这样的业务规则。
-
领域服务
- 当某个操作或业务逻辑不适合放在实体或值对象中时(因为它涉及多个实体,或者是一个无状态的过程),就可以将其封装为领域服务。
- 例子:“资金转账”服务,它需要操作“转出账户”和“转入账户”两个实体,并遵守转账规则。
-
领域事件
- 表示在领域中发生的、具有业务意义的事情。它用于在同一个限界上下文内部或不同限界上下文之间进行异步通信,实现最终一致性。
- 例子:
OrderConfirmedEvent(订单已确认事件)。订单上下文发布该事件,库存上下文、积分上下文等订阅该事件,并相应地减少库存、增加积分。
-
仓储
- 提供一种类似集合的接口,用于持久化和检索聚合。它封装了数据访问的细节,让领域层可以专注于业务逻辑,而不必关心底层是数据库还是其他存储机制。
- 关键点:仓储的接口定义在领域层,实现在基础设施层。
-
应用服务
- 位于领域模型之上,负责协调应用程序的活动。它不包含业务规则,而是将任务委托给领域对象(实体、领域服务)来完成。
- 职责:事务控制、安全认证、调用仓储获取聚合、调用领域模型执行业务操作、发布领域事件等。
3. DDD 的分层架构
一个典型的遵循 DDD 的应用程序会采用分层架构,以确保关注点分离:
- 用户界面层:负责向用户展示信息和解释用户指令。
- 应用层:包含应用服务,薄薄的一层,协调任务,不包含业务逻辑。
- 领域层:核心,包含业务逻辑的模型(实体、值对象、领域服务、领域事件等)。
- 基础设施层:为其他层提供技术支持,如数据库持久化(实现仓储)、消息通信、文件访问等。
依赖方向:外层可以依赖内层,而内层绝不能依赖外层。领域层是核心,它应该是独立的。
4. 何时使用 DDD?
DDD 并非银弹,它有其适用场景:
-
推荐使用:
- 业务逻辑非常复杂、核心的系统。
- 需要长期迭代、维护和演进的项目。
- 团队规模较大,需要清晰的模块边界来协作。
-
不推荐使用:
- “CRUD”型应用,业务逻辑简单。
- 一次性项目或原型。
- 团队对业务领域理解不深,且没有业务专家配合。
5. DDD的落地案例
为你展示一个基于 Spring Boot 的 DDD 项目实战案例。我们将构建一个简化的银行账户管理系统。
项目结构与技术栈
项目分层结构
src/main/java/com/example/bank/
├── application/ # 应用层
│ ├── AccountApplicationService.java
│ └── dto/
├── domain/ # 领域层 - 核心
│ ├── model/
│ │ ├── Account.java
│ │ ├── AccountId.java
│ │ ├── AccountNumber.java
│ │ ├── Money.java
│ │ └── Transaction.java
│ ├── service/
│ │ └── TransferService.java
│ ├── event/
│ │ └── AccountCreatedEvent.java
│ └── repository/
│ └── AccountRepository.java
├── infrastructure/ # 基础设施层
│ ├── persistence/
│ │ ├── AccountRepositoryImpl.java
│ │ ├── entity/
│ │ └── mapper/
│ └── external/
├── interfaces/ # 用户接口层
│ ├── web/
│ │ ├── AccountController.java
│ │ └── dto/
└── BankApplication.java
实际项目中的分层结构
Maven 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
领域层实现
1. 实体 - 账户 (聚合根)
// domain/model/Account.java
package com.example.bank.domain.model;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class Account {
private AccountId id;
private AccountNumber accountNumber;
private Money balance;
private String accountHolder;
private LocalDateTime createdAt;
private List<Transaction> transactions = new ArrayList<>();
// 核心业务逻辑:存款
public void deposit(Money amount) {
if (amount.isNegativeOrZero()) {
throw new IllegalArgumentException("存款金额必须大于0");
}
this.balance = this.balance.add(amount);
Transaction transaction = Transaction.deposit(this.id, amount);
this.transactions.add(transaction);
}
// 核心业务逻辑:取款
public void withdraw(Money amount) {
if (amount.isNegativeOrZero()) {
throw new IllegalArgumentException("取款金额必须大于0");
}
if (this.balance.isLessThan(amount)) {
throw new IllegalArgumentException("余额不足");
}
this.balance = this.balance.subtract(amount);
Transaction transaction = Transaction.withdrawal(this.id, amount);
this.transactions.add(transaction);
}
// 工厂方法:创建新账户
public static Account create(AccountNumber accountNumber, String accountHolder, Money initialDeposit) {
Account account = new Account();
account.id = AccountId.nextId();
account.accountNumber = accountNumber;
account.accountHolder = accountHolder;
account.balance = initialDeposit;
account.createdAt = LocalDateTime.now();
if (!initialDeposit.isZero()) {
Transaction transaction = Transaction.deposit(account.id, initialDeposit);
account.transactions.add(transaction);
}
// 发布领域事件
DomainEventPublisher.publish(new AccountCreatedEvent(account.id, account.accountNumber));
return account;
}
// Getters
public AccountId getId() { return id; }
public AccountNumber getAccountNumber() { return accountNumber; }
public Money getBalance() { return balance; }
public String getAccountHolder() { return accountHolder; }
public List<Transaction> getTransactions() { return new ArrayList<>(transactions); }
}
2. 值对象 - 金额
// domain/model/Money.java
package com.example.bank.domain.model;
import javax.persistence.Embeddable;
import java.math.BigDecimal;
import java.util.Objects;
@Embeddable
public class Money {
private static final BigDecimal MIN_AMOUNT = BigDecimal.ZERO;
private static final BigDecimal MAX_AMOUNT = new BigDecimal("1000000000");
private final BigDecimal amount;
private final String currency;
protected Money() {
// for JPA
this.amount = BigDecimal.ZERO;
this.currency = "CNY";
}
public Money(BigDecimal amount, String currency) {
validateAmount(amount);
this.amount = amount.setScale(2, BigDecimal.ROUND_HALF_EVEN);
this.currency = currency;
}
private void validateAmount(BigDecimal amount) {
if (amount == null || amount.compareTo(MIN_AMOUNT) < 0) {
throw new IllegalArgumentException("金额不能为负数");
}
if (amount.compareTo(MAX_AMOUNT) > 0) {
throw new IllegalArgumentException("金额超出系统限制");
}
}
// 业务操作
public Money add(Money other) {
validateCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}
public Money subtract(Money other) {
validateCurrency(other);
return new Money(this.amount.subtract(other.amount), this.currency);
}
public boolean isGreaterThan(Money other) {
validateCurrency(other);
return this.amount.compareTo(other.amount) > 0;
}
public boolean isLessThan(Money other) {
validateCurrency(other);
return this.amount.compareTo(other.amount) < 0;
}
public boolean isNegativeOrZero() {
return this.amount.compareTo(BigDecimal.ZERO) <= 0;
}
public boolean isZero() {
return this.amount.compareTo(BigDecimal.ZERO) == 0;
}
private void validateCurrency(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不匹配");
}
}
// Getters, equals, hashCode
public BigDecimal getAmount() { return amount; }
public String getCurrency() { return currency; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 &&
Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}
3. 值对象 - 账户号
// domain/model/AccountNumber.java
package com.example.bank.domain.model;
import javax.persistence.Embeddable;
import java.util.Objects;
import java.util.regex.Pattern;
@Embeddable
public class AccountNumber {
private static final Pattern PATTERN = Pattern.compile("^\\d{16}$");
private final String number;
protected AccountNumber() {
// for JPA
this.number = null;
}
public AccountNumber(String number) {
validateNumber(number);
this.number = number;
}
private void validateNumber(String number) {
if (number == null || !PATTERN.matcher(number).matches()) {
throw new IllegalArgumentException("账户号必须是16位数字");
}
}
public String getNumber() {
return number;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AccountNumber that = (AccountNumber) o;
return Objects.equals(number, that.number);
}
@Override
public int hashCode() {
return Objects.hash(number);
}
@Override
public String toString() {
return number;
}
}
4. 领域服务 - 转账服务
// domain/service/TransferService.java
package com.example.bank.domain.service;
import com.example.bank.domain.model.Account;
import com.example.bank.domain.model.Money;
import com.example.bank.domain.repository.AccountRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class TransferService {
private final AccountRepository accountRepository;
public TransferService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public void transfer(Account fromAccount, Account toAccount, Money amount) {
// 执行转账业务规则
validateTransfer(fromAccount, toAccount, amount);
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
// 这里可以发布转账成功领域事件
// DomainEventPublisher.publish(new TransferCompletedEvent(...));
}
private void validateTransfer(Account fromAccount, Account toAccount, Money amount) {
if (fromAccount.equals(toAccount)) {
throw new IllegalArgumentException("不能向自己转账");
}
if (amount.isNegativeOrZero()) {
throw new IllegalArgumentException("转账金额必须大于0");
}
// 其他业务规则...
}
}
5. 领域事件
// domain/event/AccountCreatedEvent.java
package com.example.bank.domain.event;
import com.example.bank.domain.model.AccountId;
import com.example.bank.domain.model.AccountNumber;
import java.time.LocalDateTime;
public class AccountCreatedEvent implements DomainEvent {
private final AccountId accountId;
private final AccountNumber accountNumber;
private final LocalDateTime occurredOn;
public AccountCreatedEvent(AccountId accountId, AccountNumber accountNumber) {
this.accountId = accountId;
this.accountNumber = accountNumber;
this.occurredOn = LocalDateTime.now();
}
// Getters
public AccountId getAccountId() { return accountId; }
public AccountNumber getAccountNumber() { return accountNumber; }
public LocalDateTime getOccurredOn() { return occurredOn; }
}
应用层实现
应用服务
// application/AccountApplicationService.java
package com.example.bank.application;
import com.example.bank.domain.model.*;
import com.example.bank.domain.service.TransferService;
import com.example.bank.domain.repository.AccountRepository;
import com.example.bank.application.dto.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class AccountApplicationService {
private final AccountRepository accountRepository;
private final TransferService transferService;
public AccountApplicationService(AccountRepository accountRepository,
TransferService transferService) {
this.accountRepository = accountRepository;
this.transferService = transferService;
}
public AccountDTO createAccount(CreateAccountCommand command) {
AccountNumber accountNumber = new AccountNumber(command.getAccountNumber());
Money initialDeposit = new Money(command.getInitialDeposit(), "CNY");
Account account = Account.create(accountNumber, command.getAccountHolder(), initialDeposit);
Account savedAccount = accountRepository.save(account);
return AccountDTO.from(savedAccount);
}
public void deposit(DepositCommand command) {
AccountId accountId = new AccountId(command.getAccountId());
Account account = accountRepository.findById(accountId)
.orElseThrow(() -> new IllegalArgumentException("账户不存在"));
Money amount = new Money(command.getAmount(), "CNY");
account.deposit(amount);
accountRepository.save(account);
}
public void transfer(TransferCommand command) {
AccountId fromAccountId = new AccountId(command.getFromAccountId());
AccountId toAccountId = new AccountId(command.getToAccountId());
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new IllegalArgumentException("转出账户不存在"));
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new IllegalArgumentException("转入账户不存在"));
Money amount = new Money(command.getAmount(), "CNY");
transferService.transfer(fromAccount, toAccount, amount);
}
public AccountDTO getAccount(String accountId) {
AccountId id = new AccountId(accountId);
Account account = accountRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("账户不存在"));
return AccountDTO.from(account);
}
}
DTO 对象
// application/dto/AccountDTO.java
package com.example.bank.application.dto;
import com.example.bank.domain.model.Account;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class AccountDTO {
private String accountId;
private String accountNumber;
private String accountHolder;
private BigDecimal balance;
private String currency;
private LocalDateTime createdAt;
// Static factory method
public static AccountDTO from(Account account) {
AccountDTO dto = new AccountDTO();
dto.setAccountId(account.getId().getId());
dto.setAccountNumber(account.getAccountNumber().getNumber());
dto.setAccountHolder(account.getAccountHolder());
dto.setBalance(account.getBalance().getAmount());
dto.setCurrency(account.getBalance().getCurrency());
dto.setCreatedAt(account.getCreatedAt());
return dto;
}
// Getters and Setters
// ...
}
基础设施层实现
仓储实现
// infrastructure/persistence/AccountRepositoryImpl.java
package com.example.bank.infrastructure.persistence;
import com.example.bank.domain.model.Account;
import com.example.bank.domain.model.AccountId;
import com.example.bank.domain.repository.AccountRepository;
import com.example.bank.infrastructure.persistence.entity.AccountEntity;
import com.example.bank.infrastructure.persistence.mapper.AccountMapper;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class AccountRepositoryImpl implements AccountRepository {
private final AccountJpaRepository accountJpaRepository;
private final AccountMapper accountMapper;
public AccountRepositoryImpl(AccountJpaRepository accountJpaRepository,
AccountMapper accountMapper) {
this.accountJpaRepository = accountJpaRepository;
this.accountMapper = accountMapper;
}
@Override
public Account save(Account account) {
AccountEntity entity = accountMapper.toEntity(account);
AccountEntity savedEntity = accountJpaRepository.save(entity);
return accountMapper.toDomain(savedEntity);
}
@Override
public Optional<Account> findById(AccountId accountId) {
return accountJpaRepository.findById(accountId.getId())
.map(accountMapper::toDomain);
}
@Override
public Optional<Account> findByAccountNumber(String accountNumber) {
return accountJpaRepository.findByAccountNumber(accountNumber)
.map(accountMapper::toDomain);
}
}
interface AccountJpaRepository extends JpaRepository<AccountEntity, String> {
Optional<AccountEntity> findByAccountNumber(String accountNumber);
}
数据实体与映射器
// infrastructure/persistence/entity/AccountEntity.java
package com.example.bank.infrastructure.persistence.entity;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "accounts")
public class AccountEntity {
@Id
private String id;
@Column(name = "account_number", unique = true, nullable = false)
private String accountNumber;
@Column(name = "account_holder", nullable = false)
private String accountHolder;
@Column(name = "balance", nullable = false)
private BigDecimal balance;
@Column(name = "currency", nullable = false)
private String currency;
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt;
// Getters and Setters
// ...
}
用户接口层实现
REST Controller
// interfaces/web/AccountController.java
package com.example.bank.interfaces.web;
import com.example.bank.application.AccountApplicationService;
import com.example.bank.application.dto.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.net.URI;
@RestController
@RequestMapping("/api/accounts")
public class AccountController {
private final AccountApplicationService accountApplicationService;
public AccountController(AccountApplicationService accountApplicationService) {
this.accountApplicationService = accountApplicationService;
}
@PostMapping
public ResponseEntity<AccountDTO> createAccount(@Valid @RequestBody CreateAccountCommand command) {
AccountDTO account = accountApplicationService.createAccount(command);
return ResponseEntity.created(URI.create("/api/accounts/" + account.getAccountId()))
.body(account);
}
@PostMapping("/{accountId}/deposit")
public ResponseEntity<Void> deposit(@PathVariable String accountId,
@Valid @RequestBody DepositRequest request) {
DepositCommand command = new DepositCommand(accountId, request.getAmount());
accountApplicationService.deposit(command);
return ResponseEntity.ok().build();
}
@PostMapping("/transfer")
public ResponseEntity<Void> transfer(@Valid @RequestBody TransferRequest request) {
TransferCommand command = new TransferCommand(
request.getFromAccountId(),
request.getToAccountId(),
request.getAmount()
);
accountApplicationService.transfer(command);
return ResponseEntity.ok().build();
}
@GetMapping("/{accountId}")
public ResponseEntity<AccountDTO> getAccount(@PathVariable String accountId) {
AccountDTO account = accountApplicationService.getAccount(accountId);
return ResponseEntity.ok(account);
}
}
配置类
主应用类
// BankApplication.java
package com.example.bank;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableJpaRepositories
public class BankApplication {
public static void main(String[] args) {
SpringApplication.run(BankApplication.class, args);
}
}
使用示例
创建账户
curl -X POST http://localhost:8080/api/accounts \
-H "Content-Type: application/json" \
-d '{
"accountNumber": "1234567890123456",
"accountHolder": "张三",
"initialDeposit": 1000.00
}'
执行转账
curl -X POST http://localhost:8080/api/accounts/transfer \
-H "Content-Type: application/json" \
-d '{
"fromAccountId": "acc-001",
"toAccountId": "acc-002",
"amount": 500.00
}'
本章节DDD实践总结
- 清晰的层次架构:严格分离领域层、应用层、基础设施层和接口层
- 丰富的领域模型:实体、值对象、领域服务各司其职
- 聚合根保护不变条件:所有对账户的修改都必须通过聚合根
- 领域事件:用于解耦领域逻辑,实现最终一致性
- 统一语言:代码中的类名、方法名都直接反映业务概念
- 依赖倒置:领域层不依赖基础设施层
这个示例展示了如何在 Spring Boot 中落地 DDD 的核心概念,你可以基于此框架继续扩展更复杂的业务功能。
总结
| 层面 | 核心概念 | 要解决的问题 |
|---|---|---|
| 战略设计 | 限界上下文、子域、上下文映射 | 系统怎么切分? 边界在哪?如何交互? |
| 战术设计 | 实体、值对象、聚合、领域服务等 | 每个边界内怎么建模? 如何实现业务逻辑? |
掌握 DDD 的本质在于思维的转变:从被动的“实现功能”转变为主动的“构建领域模型”。通过战略设计划分清晰的边界,再通过战术设计在边界内构建一个富有表现力的模型,最终打造出能够与业务共同成长的、高质量的软件。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)