IMO ทั้งRepository
นามธรรมและสิ่งที่UnitOfWork
เป็นนามธรรมมีสถานที่ที่มีคุณค่ามากในการพัฒนาที่มีความหมาย ผู้คนจะโต้แย้งเกี่ยวกับรายละเอียดการใช้งาน แต่เช่นเดียวกับที่มีหลายวิธีในการสกินแมวมีหลายวิธีในการใช้นามธรรม
คำถามของคุณคือใช้หรือไม่ใช้และทำไม
อย่างที่คุณไม่ต้องสงสัยเลยว่าคุณมีรูปแบบทั้งสองนี้อยู่แล้วใน Entity Framework DbContext
นั่นคือUnitOfWork
และDbSet
คือRepository
. โดยทั่วไปคุณไม่จำเป็นต้องทดสอบหน่วยUnitOfWork
หรือRepository
ตัวเองเนื่องจากเป็นเพียงการอำนวยความสะดวกระหว่างชั้นเรียนของคุณและการใช้งานการเข้าถึงข้อมูลพื้นฐาน สิ่งที่คุณจะพบว่าตัวเองต้องทำซ้ำแล้วซ้ำอีกคือล้อเลียนนามธรรมทั้งสองนี้เมื่อหน่วยทดสอบตรรกะของบริการของคุณ
คุณสามารถล้อเลียนปลอมหรืออะไรก็ได้ด้วยไลบรารีภายนอกโดยเพิ่มเลเยอร์ของการอ้างอิงโค้ด(ที่คุณไม่ได้ควบคุม)ระหว่างตรรกะที่ทำการทดสอบและตรรกะที่กำลังทดสอบ
ประเด็นเล็กน้อยก็คือการมีนามธรรมของคุณเองUnitOfWork
และRepository
ช่วยให้คุณควบคุมและยืดหยุ่นได้สูงสุดเมื่อจำลองการทดสอบหน่วยของคุณ
ทั้งหมดเป็นอย่างดี แต่สำหรับฉันพลังที่แท้จริงของแนวคิดเหล่านี้เป็นสิ่งที่พวกเขาให้เป็นวิธีที่ง่ายที่จะใช้มุมมองเชิงเทคนิคการเขียนโปรแกรมและเป็นไปตามหลักการ SOLID
ดังนั้นคุณมีIRepository
:
public interface IRepository<T>
where T : class
{
T Add(T entity);
void Delete(T entity);
IQueryable<T> AsQueryable();
}
และการนำไปใช้:
public class Repository<T> : IRepository<T>
where T : class
{
private readonly IDbSet<T> _dbSet;
public Repository(PPContext context)
{
_dbSet = context.Set<T>();
}
public T Add(T entity)
{
return _dbSet.Add(entity);
}
public void Delete(T entity)
{
_dbSet.Remove(entity);
}
public IQueryable<T> AsQueryable()
{
return _dbSet.AsQueryable();
}
}
ไม่มีอะไรออกจากสามัญเพื่อให้ห่างไกล แต่ตอนนี้เราต้องการที่จะเพิ่มเข้าสู่ระบบบาง - ง่ายกับการเข้าสู่ระบบมัณฑนากร
public class RepositoryLoggerDecorator<T> : IRepository<T>
where T : class
{
Logger logger = LogManager.GetCurrentClassLogger();
private readonly IRepository<T> _decorated;
public RepositoryLoggerDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
T added = _decorated.Add(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
return added;
}
public void Delete(T entity)
{
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
_decorated.Delete(entity);
logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable();
}
}
ทั้งหมดที่ทำและมีการเปลี่ยนแปลงรหัสที่มีอยู่ของเรา มีข้อกังวลอื่น ๆ อีกมากมายที่เราสามารถเพิ่มได้เช่นการจัดการข้อยกเว้นการแคชข้อมูลการตรวจสอบข้อมูลหรืออะไรก็ตามและตลอดกระบวนการออกแบบและสร้างสิ่งที่มีค่าที่สุดที่เรามีซึ่งช่วยให้เราสามารถเพิ่มคุณสมบัติง่ายๆโดยไม่ต้องเปลี่ยนแปลงโค้ดที่มีอยู่ คือเราIRepository
เป็นนามธรรม
หลายครั้งที่ฉันเห็นคำถามนี้ใน StackOverflow -“ คุณจะทำให้ Entity Framework ทำงานในสภาพแวดล้อมแบบผู้เช่าหลายคนได้อย่างไร”
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
หากคุณมีRepository
นามธรรมคำตอบก็คือ“ เพิ่มมัณฑนากรได้ง่ายๆ”
public class RepositoryTennantFilterDecorator<T> : IRepository<T>
where T : class
{
public readonly IRepository<T> _decorated;
public RepositoryTennantFilterDecorator(IRepository<T> decorated)
{
_decorated = decorated;
}
public T Add(T entity)
{
return _decorated.Add(entity);
}
public void Delete(T entity)
{
_decorated.Delete(entity);
}
public IQueryable<T> AsQueryable()
{
return _decorated.AsQueryable().Where(o => true);
}
}
IMO คุณควรวางสิ่งที่เป็นนามธรรมอย่างง่ายไว้เหนือองค์ประกอบของบุคคลที่สามซึ่งจะถูกอ้างถึงในสถานที่มากกว่าไม่กี่แห่ง จากมุมมองนี้ ORM เป็นตัวเลือกที่สมบูรณ์แบบเนื่องจากมีการอ้างอิงในโค้ดส่วนใหญ่ของเรา
คำตอบที่มักจะนึกขึ้นได้เมื่อมีคนพูดว่า "ทำไมฉันจึงควรมีนามธรรม (เช่นRepository
) บนสิ่งนี้หรือห้องสมุดของบุคคลที่สาม" คือ "ทำไมคุณไม่ทำล่ะ"
PS ตกแต่งที่เรียบง่ายมากที่จะนำไปใช้โดยใช้ภาชนะ IOC เช่นSimpleInjector
[TestFixture]
public class IRepositoryTesting
{
[Test]
public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
{
Container container = new Container();
container.RegisterLifetimeScope<PPContext>();
container.RegisterOpenGeneric(
typeof(IRepository<>),
typeof(Repository<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryLoggerDecorator<>));
container.RegisterDecorator(
typeof(IRepository<>),
typeof(RepositoryTennantFilterDecorator<>));
container.Verify();
using (container.BeginLifetimeScope())
{
var result = container.GetInstance<IRepository<Image>>();
Assert.That(
result,
Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
Assert.That(
(result as RepositoryTennantFilterDecorator<Image>)._decorated,
Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
}
}
}