Android 区块链 + CleanArchitecture + MVI 架构实践
文章目录
- [Android 区块链 + CleanArchitecture + MVI 架构实践](#Android 区块链 + CleanArchitecture + MVI 架构实践)
-
- 前言
- 项目概述--核心特性
- [Clean Architecture 架构的三层分离设计理念](#Clean Architecture 架构的三层分离设计理念)
-
- [Presentation Layer(表现层)](#Presentation Layer(表现层))
- [Domain Layer(领域层)](#Domain Layer(领域层))
- [Data Layer(数据层)](#Data Layer(数据层))
- [MVI 模式深度解析](#MVI 模式深度解析)
- [Room 数据库架构设计](#Room 数据库架构设计)
- [Solana Mobile SDK 集成](#Solana Mobile SDK 集成)
- [依赖注入架构--Hilt 模块配置](#依赖注入架构--Hilt 模块配置)
- [UI 设计与 Jetpack Compose](#UI 设计与 Jetpack Compose)
- 性能优化策略
-
- [1. Lazy Loading](#1. Lazy Loading)
- [2. 状态管理优化](#2. 状态管理优化)
- [3. 数据库优化](#3. 数据库优化)
- 测试策略
-
- 单元测试
- [UI 测试](#UI 测试)
- 架构优势总结
-
- [1. 可维护性](#1. 可维护性)
- [2. 可测试性](#2. 可测试性)
- [3. 可扩展性](#3. 可扩展性)
- [4. 开发效率](#4. 开发效率)
- 与其他架构的对比
- 总结
前言
在区块链技术快速发展的今天,去中心化应用(DApp)正在重新定义传统的应用开发模式。本文将深入探讨如何在Android 平台上构建一个基于 Solana Mobile SDK 的去中心化电商平台,采用 Clean Architecture 和 MVI架构模式,实现高度解耦、可测试和可维护的现代化应用。
项目源码地址 在结尾

项目概述--核心特性
我们构建的是一个完整的 Web3 去中心化电商平台,集成了购买、销售、收益和治理四大核心功能。该项目不仅展示了区块链技术在电商领域的应用潜力,更重要的是展现了现代 Android 开发的最佳实践:
- 去中心化交易: 基于 Solana 区块链的安全交易
- 钱包集成: 无缝集成 Solana Mobile Wallet Adapter
- 现代 UI: Jetpack Compose + Material Design 3
- 响应式架构: MVI 模式确保单向数据流
- 离线支持: Room 数据库提供本地数据持久化
- 科技美学: 深色主题配合霓虹色彩的未来科技风格
Clean Architecture 架构的三层分离设计理念
我们严格遵循 Uncle Bob 的 Clean Architecture 原则,将应用分为三个独立的层次:
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Intent │ │ ViewModel │ │ State │ │
│ │ │ │ │ │ │ │
│ │ UserAction │→ │ StateFlow │→ │ UIState │ │
│ │ SystemEvent │ │ SharedFlow │ │ Effect │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Domain Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Entity │ │ Use Case │ │ Repository │ │
│ │ │ │ │ │ Interface │ │
│ │ Product │ │ RegisterMer │ │ IMerchant │ │
│ │ Order │ │ chantUseCase│ │ Repository │ │
│ │ Merchant │ │ GetProducts │ │ IProduct │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Data Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Repository │ │ DataSource │ │ Mapper │ │
│ │ Implement │ │ │ │ │ │
│ │ │ │ Mock/Room │ │ DTO ↔ Entity│ │
│ │ ProductRepo │ │ Solana/API │ │ Serialization│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
Presentation Layer(表现层)
表现层负责 UI 渲染和用户交互,采用 MVI 模式确保单向数据流:
```kotlin
// Intent - 用户意图的抽象表示
sealed class ProductIntent {
object LoadProducts : ProductIntent()
data class SearchProducts(val query: String) : ProductIntent()
data class FilterProducts(val filter: ProductFilter) : ProductIntent()
}
// State - 不可变的 UI 状态
data class ProductState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
val searchQuery: String = ""
)
// ViewModel - 状态管理和业务逻辑协调
class ProductViewModel @Inject constructor(
private val getProductsUseCase: GetProductsUseCase
) : ViewModel() {
private val _state = MutableStateFlow(ProductState())
val state: StateFlow<ProductState> = _state.asStateFlow()
fun handleIntent(intent: ProductIntent) {
when (intent) {
is ProductIntent.LoadProducts -> loadProducts()
is ProductIntent.SearchProducts -> searchProducts(intent.query)
is ProductIntent.FilterProducts -> filterProducts(intent.filter)
}
}
}
```
Domain Layer(领域层)
领域层包含纯粹的业务逻辑,不依赖任何框架:
```kotlin
// Entity - 业务实体
data class Product(
val id: String,
val name: String,
val description: String,
val price: BigDecimal,
val currency: String,
val imageUrls: List<String>,
val sellerId: String,
val category: String,
val stock: Int,
val rating: Float,
val reviewCount: Int
)
// Use Case - 业务用例
class GetProductsUseCase @Inject constructor(
private val productRepository: IProductRepository
) {
suspend operator fun invoke(): Flow<Result<List<Product>>> {
return productRepository.getProducts()
.map { products ->
Result.Success(products.sortedByDescending { it.rating })
}
.catch { exception ->
emit(Result.Error(exception))
}
}
}
// Repository Interface - 数据访问抽象
interface IProductRepository {
suspend fun getProducts(): Flow<List<Product>>
suspend fun getProductById(id: String): Product?
suspend fun searchProducts(query: String): Flow<List<Product>>
}
```
Data Layer(数据层)
数据层实现具体的数据访问逻辑,支持多种数据源:
```kotlin
// Repository Implementation
class ProductRepositoryImpl @Inject constructor(
private val mockDataSource: MockProductDataSource,
private val roomDataSource: RoomProductDataSource,
private val solanaDataSource: SolanaProductDataSource
) : IProductRepository {
override suspend fun getProducts(): Flow<List<Product>> {
return combine(
roomDataSource.getProducts(),
solanaDataSource.getProducts()
) { localProducts, blockchainProducts ->
// 合并本地和区块链数据
(localProducts + blockchainProducts).distinctBy { it.id }
}
}
}
// Data Source Abstraction
interface ProductDataSource {
suspend fun getProducts(): Flow<List<Product>>
suspend fun getProductById(id: String): Product?
suspend fun insertProducts(products: List<Product>)
}
```
MVI 模式深度解析
单向数据流的优势
MVI 模式通过强制单向数据流,解决了传统 MVP/MVVM 模式中状态管理的复杂性:
User Interaction → Intent → ViewModel → State → UI → User Interaction
状态管理策略
我们使用 StateFlow 和 SharedFlow 来管理不同类型的状态:
kotlin
class MainViewModel @Inject constructor(
private val solanaWalletConnectUseCase: SolanaWalletConnectUseCase
) : ViewModel() {
// UI 状态 - 使用 StateFlow
private val _uiState = MutableStateFlow(MainUiState())
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
// 一次性事件 - 使用 SharedFlow
private val _uiEffect = MutableSharedFlow<UiEffect>()
val uiEffect: SharedFlow<UiEffect> = _uiEffect.asSharedFlow()
fun handleIntent(intent: MainIntent) {
viewModelScope.launch {
when (intent) {
is MainIntent.ConnectWallet -> connectWallet()
is MainIntent.DisconnectWallet -> disconnectWallet()
}
}
}
}
Room 数据库架构设计
数据库设计
我们采用 Room 数据库来提供离线支持和数据缓存:
kotlin
@Database(
entities = [ProductEntity::class],
version = 1,
exportSchema = false
)
@TypeConverters(StringListConverter::class, StringMapConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun productDao(): ProductDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"web3_ecommerce_database"
).build()
INSTANCE = instance
instance
}
}
}
}
类型转换器
为了支持复杂数据类型的存储,我们实现了自定义类型转换器:
kotlin
class StringListConverter {
@TypeConverter
fun fromStringList(value: List<String>): String {
return Json.encodeToString(value)
}
@TypeConverter
fun toStringList(value: String): List<String> {
return Json.decodeFromString(value)
}
}
class StringMapConverter {
@TypeConverter
fun fromStringMap(value: Map<String, String>): String {
return Json.encodeToString(value)
}
@TypeConverter
fun toStringMap(value: String): Map<String, String> {
return Json.decodeFromString(value)
}
}
Solana Mobile SDK 集成
钱包连接
我们通过 Solana Mobile Wallet Adapter 实现钱包连接:
kotlin
class SolanaWalletConnectUseCase @Inject constructor(
private val mobileWalletAdapter: MobileWalletAdapter
) {
suspend fun connect(): Result<Connected> {
return try {
val result = mobileWalletAdapter.transact { sender ->
sender.authorize(
identityUri = Uri.parse("https://web3-ecommerce.app"),
iconUri = Uri.parse("favicon.ico"),
identityName = "Web3 Ecommerce"
)
}
Result.Success(Connected(result.publicKey, result.accountLabel))
} catch (e: Exception) {
Result.Error(e)
}
}
}
区块链交易
通过 Solana SDK 实现商品交易和支付:
kotlin
class SolanaTransactionUseCase @Inject constructor(
private val mobileWalletAdapter: MobileWalletAdapter,
private val solanaTokenUtils: SolanaTokenUtils
) {
suspend fun purchaseProduct(
productId: String,
amount: BigDecimal,
sellerAddress: String
): Result<String> {
return try {
val transaction = solanaTokenUtils.createTransferTransaction(
amount = amount,
recipientAddress = sellerAddress
)
val result = mobileWalletAdapter.transact { sender ->
sender.signAndSendTransactions(arrayOf(transaction))
}
Result.Success(result.signatures.first())
} catch (e: Exception) {
Result.Error(e)
}
}
}
依赖注入架构--Hilt 模块配置
我们使用 Hilt 来管理依赖注入,确保各层之间的解耦:
kotlin
@Module
@InstallIn(SingletonComponent::class)
abstract class DataModule {
@Binds
abstract fun bindProductRepository(
productRepositoryImpl: ProductRepositoryImpl
): IProductRepository
@Binds
abstract fun bindMerchantRepository(
merchantRepositoryImpl: MerchantRepositoryImpl
): IMerchantRepository
}
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return AppDatabase.getDatabase(context.applicationContext)
}
@Provides
fun provideProductDao(database: AppDatabase): ProductDao {
return database.productDao()
}
@Provides
@Singleton
fun provideMobileWalletAdapter(@ApplicationContext context: Context): MobileWalletAdapter {
return MobileWalletAdapter()
}
}
UI 设计与 Jetpack Compose
科技美学设计系统
我们采用深色主题配合霓虹色彩,营造未来科技氛围:
kotlin
@Composable
fun Web3EcommerceTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) {
darkColorScheme(
primary = TechBlue,
secondary = NeonGreen,
tertiary = NeonPurple,
background = DeepBlack,
surface = DarkGray,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.White,
onSurface = Color.White
)
} else {
lightColorScheme()
}
MaterialTheme(
colorScheme = colorScheme,
typography = TechTypography,
content = content
)
}
可复用组件设计
我们创建了一系列可复用的 Compose 组件:
kotlin
@Composable
fun TechButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true
) {
Button(
onClick = onClick,
modifier = modifier
.glowEffect(enabled)
.clip(RoundedCornerShape(8.dp)),
enabled = enabled,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Text(
text = text,
style = MaterialTheme.typography.labelLarge
)
}
}
@Composable
fun ProductCard(
product: Product,
onProductClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
Card(
modifier = modifier
.fillMaxWidth()
.clickable { onProductClick(product.id) }
.glowEffect(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
AsyncImage(
model = product.imageUrls.firstOrNull(),
contentDescription = product.name,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = product.name,
style = MaterialTheme.typography.titleMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = "${product.price} ${product.currency}",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
)
}
}
}
性能优化策略
1. Lazy Loading
使用 LazyColumn 和 LazyGrid 实现列表虚拟化:
kotlin
@Composable
fun ProductGrid(
products: List<Product>,
onProductClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = modifier,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(products) { product ->
ProductCard(
product = product,
onProductClick = onProductClick
)
}
}
}
2. 状态管理优化
使用 remember 和 derivedStateOf 优化重组:
kotlin
@Composable
fun ProductScreen(
viewModel: ProductViewModel = hiltViewModel()
) {
val state by viewModel.state.collectAsState()
// 使用 derivedStateOf 避免不必要的重组
val filteredProducts by remember {
derivedStateOf {
state.products.filter { product ->
product.name.contains(state.searchQuery, ignoreCase = true)
}
}
}
LaunchedEffect(Unit) {
viewModel.handleIntent(ProductIntent.LoadProducts)
}
ProductGrid(
products = filteredProducts,
onProductClick = { productId ->
// 导航到产品详情
}
)
}
3. 数据库优化
通过索引和查询优化提升数据库性能:
kotlin
@Entity(
tableName = "products",
indices = [
Index(value = ["category"]),
Index(value = ["seller_id"]),
Index(value = ["price"])
]
)
data class ProductEntity(
@PrimaryKey val id: String,
val name: String,
val description: String,
val price: Double,
val currency: String,
@ColumnInfo(name = "image_urls") val imageUrls: List<String>,
@ColumnInfo(name = "seller_id") val sellerId: String,
val category: String,
val stock: Int,
val rating: Float,
@ColumnInfo(name = "review_count") val reviewCount: Int,
@ColumnInfo(name = "created_at") val createdAt: Long,
@ColumnInfo(name = "updated_at") val updatedAt: Long
)
测试策略
单元测试
我们为每一层都编写了相应的单元测试:
kotlin
// Domain Layer 测试
class GetProductsUseCaseTest {
@Mock
private lateinit var productRepository: IProductRepository
private lateinit var getProductsUseCase: GetProductsUseCase
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
getProductsUseCase = GetProductsUseCase(productRepository)
}
@Test
fun `should return sorted products by rating`() = runTest {
// Given
val products = listOf(
createProduct(id = "1", rating = 4.0f),
createProduct(id = "2", rating = 4.5f),
createProduct(id = "3", rating = 3.5f)
)
whenever(productRepository.getProducts()).thenReturn(flowOf(products))
// When
val result = getProductsUseCase().first()
// Then
assertTrue(result is Result.Success)
val sortedProducts = (result as Result.Success).data
assertEquals("2", sortedProducts.first().id)
assertEquals("3", sortedProducts.last().id)
}
}
UI 测试
使用 Compose Testing 进行 UI 测试:
kotlin
@HiltAndroidTest
class ProductScreenTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test
fun should_display_products_when_loaded() {
// Given
val products = listOf(
createProduct(name = "Test Product 1"),
createProduct(name = "Test Product 2")
)
// When
composeTestRule.setContent {
Web3EcommerceTheme {
ProductScreen()
}
}
// Then
composeTestRule.onNodeWithText("Test Product 1").assertIsDisplayed()
composeTestRule.onNodeWithText("Test Product 2").assertIsDisplayed()
}
}
架构优势总结
1. 可维护性
- 模块化设计: 各层独立,职责清晰
- 依赖倒置: 高层模块不依赖低层模块
- 接口抽象: 通过接口实现解耦
2. 可测试性
- 依赖注入: 便于 Mock 和单元测试
- 纯函数: Domain Layer 易于测试
- UI 测试: Compose Testing 支持完整测试
3. 可扩展性
- 插件化架构: 数据源可灵活切换
- 功能模块: 新功能独立开发
- 多平台支持: 架构支持扩展
4. 开发效率
- 并行开发: 各层可并行开发
- 快速迭代: Mock 数据支持快速原型
- 离线开发: Room 数据库支持离线访问
- 类型安全: Kotlin 类型系统减少错误
与其他架构的对比
特性 | Clean Architecture | MVI (Model-View-Intent) | MVVM | MVP | MVC |
---|---|---|---|---|---|
核心思想 | 关注点分离,依赖反转,将业务逻辑与框架解耦。 | 单向数据流 (Unidirectional Data Flow),通过不可变状态管理 UI。 | 响应式数据绑定,View 自动随 ViewModel 变化而更新。 | View 与 Presenter 严格分离,Presenter 负责所有逻辑。 | 业务逻辑与 UI 分离,Controller 作为中介。 |
复杂度 | 高 (涉及多层抽象和严格规则) | 中高 (需处理 Intent 和 State 管理) | 中 (ViewModel 和数据绑定) | 中 (需要定义 View 和 Presenter 接口) | 低 (模式简单直接) |
可测试性 | 极高 (核心业务逻辑完全独立) | 极高 (UI 状态可预测,逻辑在 ViewModel 中) | 高 (ViewModel 独立于 View 测试) | 高 (Presenter 独立于 View 测试) | 低 (逻辑常与 View 耦合) |
学习成本 | 高 (概念多,如 Use Case、Repository) | 中高 (需理解单向数据流和不可变状态) | 中 (需理解 LiveData/StateFlow 和数据绑定) | 中 (接口通信和生命周期管理) | 低 |
适用项目 | 大型复杂、企业级应用,需要长期维护和扩展。 | 中大型,需要明确、可预测的 UI 状态管理。 | 中大型,适合需要响应式UI和数据绑定的项目。 | 中型,适用于业务逻辑较复杂的项目。 | 小型、简单的应用或作为入门学习。 |
维护成本 | 低(长期),易于修改、扩展,影响范围小。 | 低(长期),状态可预测,调试方便。 | 中,ViewModel 可能会变得臃肿。 | 中,Presenter 可能会变得臃肿。 | 高(长期),代码容易混乱,难以维护。 |
总结
通过采用 Clean Architecture + MVI 架构模式,结合 Solana Mobile SDK、Jetpack Compose 和 Room 数据库等现代技术栈,我们成功构建了一个高质量的去中心化电商应用。这个架构不仅确保了代码的可维护性和可测试性,还为未来的功能扩展和技术演进奠定了坚实的基础。
在 Web3 时代,这样的架构设计将成为构建复杂去中心化应用的标准模式。通过合理的分层设计、清晰的依赖关系和现代化的开发工具,我们能够在保证用户体验的同时,充分发挥区块链技术的优势。
项目地址 : https://github.com/chenjim/Web3-MVI-android
技术栈: Kotlin, Jetpack Compose, Solana Mobile SDK, Room Database, Hilt, MVI, Clean Architecture