Last active
July 8, 2024 15:35
-
-
Save j5ik2o/1b9041f25af43818060928aad554d3cd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 雑に書いているので、あろうことかエラーメッセージは日本語です。 | |
import scala.collection.mutable | |
// Domain Layer (unchanged) | |
case class ProductId(value: String) | |
case class ShippingProviderId(value: String) | |
enum ProductStatus: | |
case Draft, Published | |
case class Product( | |
id: ProductId, | |
name: String, | |
status: ProductStatus, | |
shippingProviderId: Option[ShippingProviderId] | |
) | |
enum DomainError: | |
case BusinessRuleViolation(message: String) | |
case RepositoryError(message: String) | |
// Purely Functional Domain Service | |
object ProductPublicationDomainService: | |
private def canPublish(product: Product): Boolean = | |
product.shippingProviderId.isDefined | |
private def publishProduct(product: Product): Either[DomainError, Product] = | |
if canPublish(product) then | |
Right(product.copy(status = ProductStatus.Published)) | |
else | |
Left(DomainError.BusinessRuleViolation(s"配送業者が設定されていません: ${product.id.value}")) | |
def publishProducts: LazyList[Product] => LazyList[Either[DomainError, Product]] = | |
_.map(publishProduct) | |
// Repository Interface | |
trait ProductRepository: | |
def findDraftProducts(): LazyList[Product] | |
def saveProducts(products: Seq[Product]): Either[DomainError, Unit] | |
// In-Memory Repository Implementation (Mutable Object) | |
class InMemoryProductRepository extends ProductRepository: | |
private val products = mutable.Map[ProductId, Product]() | |
def addProduct(product: Product): Unit = | |
products += (product.id -> product) | |
def findDraftProducts(): LazyList[Product] = | |
LazyList.from(products.values).filter(_.status == ProductStatus.Draft) | |
def saveProducts(products: Seq[Product]): Either[DomainError, Unit] = | |
try | |
products.foreach(p => this.products += (p.id -> p)) | |
Right(()) | |
catch | |
case e: Exception => Left(DomainError.RepositoryError(s"バッチ保存中にエラーが発生しました: ${e.getMessage}")) | |
// Application Layer | |
case class PublishProductsResult( | |
successCount: Int, | |
failureCount: Int, | |
errors: LazyList[DomainError] | |
) | |
object PublishProductsUseCase: | |
def execute( | |
productRepository: ProductRepository, | |
batchSize: Int = 1000 | |
): LazyList[PublishProductsResult] = | |
ProductPublicationDomainService.publishProducts(productRepository.findDraftProducts()) | |
.grouped(batchSize) | |
.map { batch => | |
val (successes, failures) = batch.partition(_.isRight) | |
val successProducts = successes.collect { case Right(product) => product } | |
val saveResult = productRepository.saveProducts(successProducts) | |
PublishProductsResult( | |
successCount = if saveResult.isRight then successProducts.size else 0, | |
failureCount = failures.size + (if saveResult.isLeft then successProducts.size else 0), | |
errors = failures.collect { case Left(error) => error } ++ saveResult.left.toSeq | |
) | |
}.to(LazyList) | |
// Usage example | |
@main def run(): Unit = | |
// リポジトリの初期化とサンプルデータの追加 | |
val repository = new InMemoryProductRepository() | |
(1 to 1000000).foreach { i => | |
repository.addProduct( | |
Product( | |
ProductId(s"P$i"), | |
s"Product $i", | |
ProductStatus.Draft, | |
if i % 3 == 0 then Some(ShippingProviderId(s"SP$i")) else None | |
) | |
) | |
} | |
println("リポジトリにサンプルデータを追加しました。処理を開始します。") | |
// Execute use case | |
val results = PublishProductsUseCase.execute(repository) | |
// Process and print results | |
var totalSuccess = 0 | |
var totalFailure = 0 | |
var totalErrors = List.empty[DomainError] | |
results.zipWithIndex.foreach { case (result, index) => | |
totalSuccess += result.successCount | |
totalFailure += result.failureCount | |
totalErrors = totalErrors ++ result.errors.take(5) // 各バッチから最大5つのエラーを記録 | |
// 10万件ごとに中間結果を表示 | |
if (index + 1) * 1000 % 100000 == 0 then | |
println(s"Processed ${(index + 1) * 1000} products") | |
println(s"Current success: $totalSuccess, Current failure: $totalFailure") | |
} | |
println(s""" | |
|最終処理結果: | |
| 成功: $totalSuccess | |
| 失敗: $totalFailure | |
| エラーサンプル (最大15件): | |
| ${totalErrors.take(15).mkString("\n ")} | |
""".stripMargin) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment