thảo luận Vấn đề về separate Repository/Service/Controller

  • Người tạo chủ đề Người tạo chủ đề soledad86
  • Ngày bắt đầu Ngày bắt đầu
K cần thừa k sao cả. Bạn có chắc là màn list k thay đổi và sau này nó cần 9/10 field thì sao?
Tôi thấy nếu anh làm thế hơi thừa. API list chỉ nên trả về vài fields cần để hiện ra ở list screen thôi. Nếu cần full thông tin thì gọi đến API get by id thì chuẩn hơn.
các bác lạc đề quá, câu hỏi của em đơn giản thôi :beat_brick: Ví dụ: màn hình list user thì chỉ có 2 thông tin: username, email, màn hình user detail thì có full thông tin. Vậy sẽ cần xây dựng 2 model cho từng màn hình? :beauty: - phía client.
Anh có thể thử kiểu này (kể cả client hay server). Tạo ra 1 MinimizedDTO chuyên dùng cho list, tạo ra 1 MaximizedDTO (extends MinimizedDTO) chuyên dùng cho get by ID. Cái MinimizedDTO anh còn có thể vứt vào DTO khác nếu cần show minimized list của thằng này. Nếu anh thấy MinimizedDTO bị thiếu field nào thì move field đấy từ MaximizedDTO sang. Tôi thấy như thế structure khá sáng sủa, dễ maitain và đảm bảo ít bị thừa thông tin.
**Về naming thì anh có thể dùng các cặp (CollapsedDTO, ExpandedDTO), (ReducedDTO, FullDTO) hoặc đơn giản chỉ là (ReducedDTO, DTO)...
 
các bác lạc đề quá, câu hỏi của em đơn giản thôi :beat_brick: Ví dụ: màn hình list user thì chỉ có 2 thông tin: username, email, màn hình user detail thì có full thông tin. Vậy sẽ cần xây dựng 2 model cho từng màn hình? :beauty: - phía client.

đang nói lí thuyết hay thực tế?

thực tế 1 hệ thống cơ bản luôn có search users, data đc index trong search engine, chỉ index những gì cần thiết. Nên ko có full users detail cho bạn ở trang list đâu.

thực tế 1 hệ thống cho dù có nhỏ thì users detail cũng chứa nhiều thứ hơn là data ở trong bảng users (tức là phải query nhiều hơn 1 lần để lấy đủ data), ko ai query hết data cho list users cả.
 
các bác cho hỏi e là việc call đến các hệ thống khác: Ví dụ: AWS S3, MS PowerBI hoặc đơn giản là 1 cái API thời tiết chẳng hạn thì các bác thường để ở đâu nhỉ? Để ở service thì cũng ko đúng lắm vì nó là business logic?
 
các bác cho hỏi e là việc call đến các hệ thống khác: Ví dụ: AWS S3, MS PowerBI hoặc đơn giản là 1 cái API thời tiết chẳng hạn thì các bác thường để ở đâu nhỉ? Để ở service thì cũng ko đúng lắm vì nó là business logic?
Cái này thì nên tách làm interface và implementation riêng.

Interface: Storage, BI, Weather -> Cái này là core của mình
Implementation: S3 implement Storage, PowerBI implement BI, AccuWeather implement Weather -> Cái này là infrastructure.

Tách làm 2 để có thể đổi implementation mà không thay đổi nghiệp vụ của core. Còn nếu mà thấy đơn giản quá, không có nhu cầu đổi impl thì thôi khỏi tách. :shame:
 
Cái này thì nên tách làm interface và implementation riêng.

Interface: Storage, BI, Weather -> Cái này là core của mình
Implementation: S3 implement Storage, PowerBI implement BI, AccuWeather implement Weather -> Cái này là infrastructure.

Tách làm 2 để có thể đổi implementation mà không thay đổi nghiệp vụ của core. Còn nếu mà thấy đơn giản quá, không có nhu cầu đổi impl thì thôi khỏi tách. :shame:
em hiểu cái Bác nói nhưng là để ở đâu nhỉ? service, repository hay ở đâu dc?
 
Tôi tìm được 1 repo ở đây viết bằng .Net Core khá giống ý tưởng với bạn thớt -https://github.com/simplcommerce/SimplCommerce, theo đó các xử lý CUD(create/update/delete) đều phải thông qua service, và 1 điểm khác là trong trường hợp query dữ liệu thì dùng repo trực tiếp. Thành thử có vẻ là dung hòa style của bạn thớt và cả cty.

  • Tôi chưa thấy chỗ họ implement caching, nhưng nếu làm tôi cho là nên làm ở các Repo.
  • Đối với các phức tạp và chạy nặng tôi chủ trương dùng RawSql ở các repo (hi sinh tính linh hoạt switch qua lại giữa các loại db)

/src/Modules/SimplCommerce.Module.Orders/Areas/Orders/Controllers/OrderController.cs đã nói:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SimplCommerce.Infrastructure.Data;
using SimplCommerce.Module.Core.Extensions;
using SimplCommerce.Module.Core.Services;
using SimplCommerce.Module.Orders.Areas.Orders.ViewModels;
using SimplCommerce.Module.Orders.Models;

namespace SimplCommerce.Module.Orders.Areas.Orders.Controllers
{
[Area("Orders")]
[Authorize]
public class OrderController : Controller
{
private readonly IMediaService _mediaService;
private readonly IRepository<Order> _orderRepository;
private readonly IWorkContext _workContext;
private readonly ICurrencyService _currencyService;

public OrderController(IRepository<Order> orderRepository, IWorkContext workContext, IMediaService mediaService, ICurrencyService currencyService)
{
_orderRepository = orderRepository;
_workContext = workContext;
_mediaService = mediaService;
_currencyService = currencyService;
}

[HttpGet("user/orders")]
public async Task<IActionResult> OrderHistoryList()
{
var user = await _workContext.GetCurrentUser();
var model = await _orderRepository
.Query()
.Where(x => x.CustomerId == user.Id && x.ParentId == null)
.Include(x => x.OrderItems).ThenInclude(x => x.Product).ThenInclude(x => x.ThumbnailImage)
.Include(x => x.OrderItems).ThenInclude(x => x.Product).ThenInclude(x => x.OptionCombinations).ThenInclude(x => x.Option)
.OrderByDescending(x => x.CreatedOn).ToListAsync();

var model2 = model.Select(x => new OrderHistoryListItem(_currencyService)
{
Id = x.Id,
CreatedOn = x.CreatedOn,
SubTotal = x.SubTotal,
OrderStatus = x.OrderStatus,
OrderItems = x.OrderItems.Select(i => new OrderHistoryProductVm
{
ProductId = i.ProductId,
ProductName = i.Product.Name,
Quantity = i.Quantity,
ThumbnailImage = i.Product.ThumbnailImage.FileName,
ProductOptions = i.Product.OptionCombinations.Select(o => o.Value)
}).ToList()
}).ToList();

foreach (var item in model2)
{
foreach (var product in item.OrderItems)
{
product.ThumbnailImage = _mediaService.GetMediaUrl(product.ThumbnailImage);
}
}

return View(model2);
}

[HttpGet("user/orders/{orderId}")]
public async Task<IActionResult> OrderDetails(long orderId)
{
var user = await _workContext.GetCurrentUser();

var order = _orderRepository
.Query()
.Include(x => x.ShippingAddress).ThenInclude(x => x.District)
.Include(x => x.ShippingAddress).ThenInclude(x => x.StateOrProvince)
.Include(x => x.ShippingAddress).ThenInclude(x => x.Country)
.Include(x => x.OrderItems).ThenInclude(x => x.Product).ThenInclude(x => x.ThumbnailImage)
.Include(x => x.OrderItems).ThenInclude(x => x.Product).ThenInclude(x => x.OptionCombinations).ThenInclude(x => x.Option)
.Include(x => x.Customer)
.FirstOrDefault(x => x.Id == orderId);

if (order == null)
{
return NotFound();
}

if (order.CustomerId != user.Id)
{
return BadRequest(new { error = "You don't have permission to view this order" });
}

var model = new OrderDetailVm(_currencyService)
{
Id = order.Id,
IsMasterOrder = order.IsMasterOrder,
CreatedOn = order.CreatedOn,
OrderStatus = (int)order.OrderStatus,
OrderStatusString = order.OrderStatus.ToString(),
CustomerId = order.CustomerId,
CustomerName = order.Customer.FullName,
CustomerEmail = order.Customer.Email,
ShippingMethod = order.ShippingMethod,
PaymentMethod = order.PaymentMethod,
PaymentFeeAmount = order.PaymentFeeAmount,
Subtotal = order.SubTotal,
DiscountAmount = order.DiscountAmount,
SubTotalWithDiscount = order.SubTotalWithDiscount,
TaxAmount = order.TaxAmount,
ShippingAmount = order.ShippingFeeAmount,
OrderTotal = order.OrderTotal,
OrderNote = order.OrderNote,
ShippingAddress = new ShippingAddressVm
{
AddressLine1 = order.ShippingAddress.AddressLine1,
CityName = order.ShippingAddress.City,
ZipCode = order.ShippingAddress.ZipCode,
ContactName = order.ShippingAddress.ContactName,
DistrictName = order.ShippingAddress.District?.Name,
StateOrProvinceName = order.ShippingAddress.StateOrProvince.Name,
Phone = order.ShippingAddress.Phone
},
OrderItems = order.OrderItems.Select(x => new OrderItemVm(_currencyService)
{
Id = x.Id,
ProductId = x.Product.Id,
ProductName = x.Product.Name,
ProductPrice = x.ProductPrice,
Quantity = x.Quantity,
DiscountAmount = x.DiscountAmount,
ProductImage = x.Product.ThumbnailImage.FileName,
TaxAmount = x.TaxAmount,
TaxPercent = x.TaxPercent,
VariationOptions = OrderItemVm.GetVariationOption(x.Product)
}).ToList()
};

foreach (var item in model.OrderItems)
{
item.ProductImage = _mediaService.GetMediaUrl(item.ProductImage);
}

return View(model);
}
}
}
 
Tôi tìm được 1 repo ở đây viết bằng .Net Core khá giống ý tưởng với bạn thớt -https://github.com/simplcommerce/SimplCommerce, theo đó các xử lý CUD(create/update/delete) đều phải thông qua service, và 1 điểm khác là trong trường hợp query dữ liệu thì dùng repo trực tiếp. Thành thử có vẻ là dung hòa style của bạn thớt và cả cty.

  • Tôi chưa thấy chỗ họ implement caching, nhưng nếu làm tôi cho là nên làm ở các Repo.
  • Đối với các phức tạp và chạy nặng tôi chủ trương dùng RawSql ở các repo (hi sinh tính linh hoạt switch qua lại giữa các loại db)

SimplCommerce của bác ThienNguyen NashTech HCM.
Ko biết bác ấy có chơi voz ko, để giải đáp cho ae 😘
 
em hiểu cái Bác nói nhưng là để ở đâu nhỉ? service, repository hay ở đâu dc?

kn bản thân:
có 2 kiểu: 3rd có sdk hoặc chỉ có api endpoints
Nếu có sdk thì ngon, ko phải làm gì hết. Nếu chỉ có endpoints để gọi api thì tự làm 1 cái sdk nhỏ cho nó, cùng level với repositories.
Service sẽ inject cả repositories và sdk cần thiết.
 
Ờ, thảm họa như nào vậy bạn? Nó chỉ thảm họa khi rơi vào tay 1 ông lead kém, không biết dẫn dắt team đi theo 1 thể thống nhất ( mà thực ra khi đã kém thì khi áp pattern vào thì nó sẽ ra 1 đống bùi nhùi, chỉ có đập đi làm lại). Chia function thôi cũng là nghệ thuật đấy!.
Tôi ví dụ đơn giản như là windows 32 API , hay linux kernel API nó toàn là 1 đống function đấy, bạn cũng chê nó là thảm họa đúng không?
Lội mãi mới có thím này cùng quan điểm.
Lắm ô cứ quan trọng hoá vde này quá lên, toàn lý thuyết suông, tuỳ team tuỳ dự án mới quyết định dùng hay ko, kinh nghiệm của mình thì ít khi dùng bởi vì:
  • Việc mất tg thiết kế 1 cấu trúc chuẩn ngay từ đầu chưa chắc là 1 ý tưởng tốt, các bạn cứ thần thánh hoá các dự án to cỡ nào ko biết, chứ thực tế nếu làm product sẽ phải chấp nhận việc thay đổi liên tục, có khi đập đi làm lại vài lần, nó là quá trình tối ưu liên tục nên cố vẽ ra các kịch bản ban đầu chả có ý nghĩa gì. Cần có 1 lead có kn dẫn dắt, chọn 1 design đơn giản, quen thuộc với cả team, build ra prototype nhanh nhất có thể, sau đó có phải thay đổi cái gì là ở bước sau.
  • Đừng cố thiết kế 1 pattern để adapt nhiều case, nhớ quy tắc 80/20, nên nhiều dự án ngta chỉ structure code để handle các logic chính, còn lại mail, log, cache, validate, vv cho hết ra 1 module hổ lốn gọi là utils, thích gọi chỗ nào thì gọi, chẳng sao cả vẫn control dc.
  • Phân tách các layer/module/structure phải nhìn từ nhiều view, đâu nhất thiết trong project đó phải có từng ấy thành phần, có thể cả project của bạn chỉ là tầng service thôi, làm sao cho nó đơn giản, sau cần bổ sung thì thêm cục mới, hoặc tách hẳn project riêng cũng dc
 
Lội thớt xíu nhưng k thấy mọi người bàn về error handling nhỉ. Nếu làm như hiện tại thì handle error cực dễ, dùng dc các error object Microsoft hỗ trợ đến tận răng cho từng case, mỗi một layer thêm vào thì độ khó để return error chuẩn tăng lên 1 bậc.
Từ controller phi thẳng xuống repo thì nó gọi là fat controller nhá, lợi: đơn giản, hại: logic bị phình to ở phía controller, khó tái sử dụng đc chỗ khác, khi nghiệp vụ phức tạp dần thì mantainace cost phi như tên lửa. Nên hay ko nên thì mình k phán ngay đc, phải hiểu project của thớt đã. Mà thôi, k chống lại đc đám đông thì thôi, nhưng nhớ ghi chép lại sự kiện này, đến lúc nào mà có issue sảy ra từ mô hình này thì counter lại mấy thằng ngày xưa phản đối chú, oke? :sexy_girl:

via theNEXTvoz for iPhone
Mình thì lại thấy maintenance như này dễ hơn do không có vụ "sửa chỗ này hỏng chỗ kia".
 
Hóng vụ 2 service gọi nhau, các bác xem cách giải quyết của e ok không: Nếu có 1 service cần gọi 1 service khác thì e sẽ tách thành 1 thằng gọi nôm na là usecase, và cả 2 thằng service đều có thể gọi usecase này
 
Hóng vụ 2 service gọi nhau, các bác xem cách giải quyết của e ok không: Nếu có 1 service cần gọi 1 service khác thì e sẽ tách thành 1 thằng gọi nôm na là usecase, và cả 2 thằng service đều có thể gọi usecase này
Lậm quá bạn.
Cứ gọi nhau như bình thường, tránh circular dependency là được. Ưu tiên tách khi là code base mới hoặc số lượng method sử dụng chung nhiều chứ đâu nhất thiết phải cứng ngắc không cho gọi nhau.
 
các bác cho hỏi e là việc call đến các hệ thống khác: Ví dụ: AWS S3, MS PowerBI hoặc đơn giản là 1 cái API thời tiết chẳng hạn thì các bác thường để ở đâu nhỉ? Để ở service thì cũng ko đúng lắm vì nó là business logic?
Để ở integration layer hoặc repository layer nha bác, integration layer sẽ gọi từ repository layer.

via theNEXTvoz for iPhone
 
Lậm quá bạn.
Cứ gọi nhau như bình thường, tránh circular dependency là được. Ưu tiên tách khi là code base mới hoặc số lượng method sử dụng chung nhiều chứ đâu nhất thiết phải cứng ngắc không cho gọi nhau.
Thường như CQRS thì chỉ có code chỗ thằng Handler, gọi thẳng qua repository, handler nó như thằng service luôn.
Còn khi nào cần code dùng chung giữa các handler thì introduce thêm thằng service nữa cũng ok.

via theNEXTvoz for iPhone
 

Thống kê chủ đề

Ngày tạo
soledad86,
Người trả lời cuối
freedom.9,
Trả lời
217
Lượt xem
33.248
Quay lại
Lên đầu trang