Ioc / DI - เหตุใดฉันจึงต้องอ้างอิงเลเยอร์ / แอสเซมบลีทั้งหมดในจุดเริ่มต้นของแอปพลิเคชัน


123

(เกี่ยวข้องกับคำถามนี้EF4: เหตุใดจึงต้องเปิดใช้งานการสร้างพร็อกซีเมื่อเปิดใช้งานการโหลดแบบขี้เกียจ )

ฉันยังใหม่กับ DI ดังนั้นอดทนกับฉัน ฉันเข้าใจว่าคอนเทนเนอร์มีหน้าที่ในการสร้างอินสแตนซ์ประเภทที่ลงทะเบียนทั้งหมดของฉัน แต่ในการดำเนินการดังกล่าวจำเป็นต้องมีการอ้างอิงถึง DLL ทั้งหมดในโซลูชันของฉันและการอ้างอิง

หากฉันไม่ได้ใช้คอนเทนเนอร์ DI ฉันจะไม่ต้องอ้างอิงไลบรารี EntityFramework ในแอป MVC3 ของฉันมีเพียงเลเยอร์ธุรกิจของฉันซึ่งจะอ้างอิงเลเยอร์ DAL / Repo ของฉัน

ฉันรู้ว่าในตอนท้ายของวัน DLL ทั้งหมดจะรวมอยู่ในโฟลเดอร์ bin แต่ปัญหาของฉันคือต้องอ้างอิงอย่างชัดเจนผ่าน "เพิ่มการอ้างอิง" ใน VS เพื่อให้สามารถเผยแพร่ WAP พร้อมไฟล์ที่จำเป็นทั้งหมดได้


1
ข้อความที่ตัดตอนมาจากหนังสือDependency Injection ใน. NET ฉบับที่สองเป็นคำตอบของทั้งมาร์คและตัวผมเองที่ละเอียดยิ่งขึ้น อธิบายโดยละเอียดเกี่ยวกับแนวคิดของComposition Rootและเหตุใดการปล่อยให้เส้นทางการเริ่มต้นของแอปพลิเคชันขึ้นอยู่กับโมดูลอื่น ๆ ทั้งหมดจึงเป็นสิ่งที่ดี
Steven

ฉันอ่านลิงก์ที่ตัดตอนมาและตอนที่ 1 ฉันจะซื้อหนังสือเล่มนี้เพราะฉันชอบการเปรียบเทียบและคำอธิบายง่ายๆสำหรับเรื่องที่ซับซ้อนของ DI ฉันคิดว่าคุณควรแนะนำคำตอบใหม่ตอบให้ชัดเจน "คุณไม่จำเป็นต้องอ้างอิงเลเยอร์ / แอสเซมบลีทั้งหมดในเลเยอร์ลอจิคัลรายการเว้นแต่จะเป็นรูทองค์ประกอบของคุณด้วย" ลิงก์ไปยังข้อความที่ตัดตอนมาและโพสต์รูปภาพรูปที่ 3 จาก สิ่งที่สกัดมา
diegohb

คำตอบ:


194

หากฉันไม่ได้ใช้คอนเทนเนอร์ DI ฉันจะไม่ต้องอ้างอิงไลบรารี EntityFramework ในแอป MVC3 ของฉันมีเพียงชั้นธุรกิจของฉันเท่านั้นที่จะอ้างอิงเลเยอร์ DAL / Repo ของฉัน

ใช่นั่นคือสถานการณ์ที่ DI ทำงานอย่างหนักเพื่อหลีกเลี่ยง :)

ด้วยรหัสที่เชื่อมโยงกันอย่างแน่นหนาแต่ละไลบรารีอาจมีการอ้างอิงเพียงเล็กน้อย แต่สิ่งเหล่านี้มีการอ้างอิงอื่น ๆ อีกครั้งสร้างกราฟเชิงลึกของการอ้างอิงเช่นนี้:

กราฟลึก

เพราะกราฟพึ่งพาอยู่ลึกก็หมายความว่าห้องสมุดส่วนใหญ่ลากไปจำนวนมากของการอ้างอิงอื่น ๆ - เช่นในแผนภาพ, ห้องสมุด Cลากพร้อมห้องสมุด H, ห้องสมุด E, J ห้องสมุด, ห้องสมุด, M, K ห้องสมุดและห้องสมุด N นี้จะทำให้มันยากที่จะนำมาใช้ห้องสมุดแต่ละอิสระจากส่วนที่เหลือ - ตัวอย่างเช่นในการทดสอบหน่วย

อย่างไรก็ตามในการประยุกต์ใช้คู่อย่างอิสระโดยการย้ายข้อมูลทั้งหมดไปยังรากองค์ประกอบที่ขึ้นกราฟจะรุนแรงบี้ :

กราฟตื้น

ตามที่แสดงด้วยสีเขียวตอนนี้คุณสามารถใช้ไลบรารี Cซ้ำได้โดยไม่ต้องลากไปตามการอ้างอิงที่ไม่ต้องการ

อย่างไรก็ตามทั้งหมดที่กล่าวด้วย DI Containers จำนวนมากคุณไม่จำเป็นต้องเพิ่มการอ้างอิงที่ยากให้กับไลบรารีที่จำเป็นทั้งหมด แต่คุณสามารถใช้การผูกล่าช้าได้ทั้งในรูปแบบของการสแกนแอสเซมบลีตามแบบแผน (แนะนำ) หรือการกำหนดค่า XML

อย่างไรก็ตามเมื่อคุณทำเช่นนั้นคุณต้องอย่าลืมคัดลอกแอสเซมบลีไปยังโฟลเดอร์ bin ของแอปพลิเคชันเพราะจะไม่เกิดขึ้นโดยอัตโนมัติอีกต่อไป โดยส่วนตัวแล้วฉันไม่ค่อยพบว่ามันคุ้มค่ากับความพยายามเพิ่มเติม

รุ่นที่ซับซ้อนมากขึ้นของคำตอบนี้สามารถพบได้ในข้อความที่ตัดตอนมานี้จากหนังสือของฉันพึ่งพาการฉีดหลักการปฏิบัติรูปแบบ


3
ขอบคุณมากตอนนี้มันสมเหตุสมผลแล้ว .. ฉันต้องการทราบว่านี่เป็นเพราะการออกแบบหรือไม่ เท่าที่บังคับใช้การอ้างอิงที่ถูกต้องฉันได้ใช้โครงการแยกกับ DI bootstrapper ของฉันเช่น Steven ที่กล่าวถึงด้านล่างซึ่งฉันอ้างอิงไลบรารีที่เหลือ โปรเจ็กต์นี้ถูกอ้างอิงโดยแอพจุดเข้าใช้งานและในตอนท้ายของบิลด์เต็มทำให้ dll ที่จำเป็นทั้งหมดอยู่ในโฟลเดอร์ bin ขอบคุณ!
diegohb

2
@Mark Seemann คำถาม / คำตอบนี้เฉพาะสำหรับ Microsoft หรือไม่ ฉันอยากทราบว่าแนวคิดในการย้ายการอ้างอิงทั้งหมดไปยัง "จุดเริ่มต้นของแอปพลิเคชัน" นี้เหมาะสมหรือไม่กับโครงการ Java EE / Spring โดยใช้ Maven ... ขอบคุณ!
Grégoire C

5
คำตอบนี้ใช้ได้กับ. NET คุณอาจต้องการอ้างถึงบทหลักการออกแบบบรรจุภัณฑ์ของ Robert C. Martin ในตัวอย่างเช่นAgile Software Development, Principles, Patterns และ Practices
Mark Seemann

7
@AndyDangerGagne รากองค์ประกอบเป็นรูปแบบ DI - The ตรงข้ามในการให้บริการผู้แทนจำหน่าย จากมุมมองของ Composition Root ไม่มีประเภทใดที่เป็น polymorphic Composition Root มองว่าทุกประเภทเป็นประเภทคอนกรีตดังนั้นหลักการทดแทน Liskov จึงไม่สามารถใช้ได้กับมัน
Mark Seemann

4
ตามกฎทั่วไปควรกำหนดอินเทอร์เฟซโดยไคลเอ็นต์ที่ใช้ ( APP, ch. 11 ) ดังนั้นหาก Library J ต้องการอินเทอร์เฟซควรกำหนดไว้ใน Library J ซึ่งเป็นข้อพิสูจน์ของหลักการ Inversion Dependency
Mark Seemann

65

หากฉันไม่ได้ใช้คอนเทนเนอร์ DI ฉันจะไม่ต้องอ้างอิงไลบรารี EntityFramework ในแอป MVC3 ของฉัน

แม้ว่าจะใช้คอนเทนเนอร์ DI คุณไม่จำเป็นต้องปล่อยให้โครงการ MVC3 ของคุณอ้างอิง EF แต่คุณ (โดยปริยาย) เลือกที่จะทำสิ่งนี้โดยใช้Composition Root (เส้นทางเริ่มต้นที่คุณเขียนกราฟวัตถุ) ภายในโครงการ MVC3 ของคุณ หากคุณเข้มงวดมากเกี่ยวกับการปกป้องขอบเขตทางสถาปัตยกรรมของคุณโดยใช้แอสเซมบลีคุณสามารถย้ายตรรกะการนำเสนอของคุณไปยังโปรเจ็กต์อื่นได้

เมื่อคุณย้ายลอจิกที่เกี่ยวข้องกับ MVC ทั้งหมด (ตัวควบคุม ฯลฯ ) จากโปรเจ็กต์เริ่มต้นไปยังไลบรารีคลาสจะช่วยให้แอสเซมบลีเลเยอร์การนำเสนอนี้ไม่เชื่อมต่อกับแอปพลิเคชันที่เหลือ โปรเจ็กต์เว็บแอปพลิเคชันของคุณเองจะกลายเป็นเปลือกบาง ๆ ด้วยตรรกะการเริ่มต้นที่จำเป็น โปรเจ็กต์เว็บแอปพลิเคชันจะเป็น Composition Root ที่อ้างอิงแอสเซมบลีอื่น ๆ ทั้งหมด

การแยกตรรกะการนำเสนอไปยังไลบรารีชั้นเรียนอาจทำให้สิ่งต่างๆซับซ้อนเมื่อทำงานกับ MVC การต่อสายทุกอย่างจะยากขึ้นเนื่องจากคอนโทรลเลอร์ไม่ได้อยู่ในโปรเจ็กต์เริ่มต้น (ในขณะที่มุมมองรูปภาพไฟล์ css น่าจะอยู่ในโปรเจ็กต์เริ่มต้น) ซึ่งอาจทำได้ แต่จะต้องใช้เวลามากกว่านี้ในการตั้งค่า

เนื่องจากข้อเสียโดยทั่วไปฉันแนะนำให้เก็บ Composition Root ไว้ในโครงการเว็บ นักพัฒนาหลายคนไม่ต้องการให้แอสเซมบลี MVC ขึ้นอยู่กับแอสเซมบลี DAL แต่นั่นไม่ใช่ปัญหาจริงๆ อย่าลืมว่าแอสเซมบลีเป็นสิ่งประดิษฐ์สำหรับการปรับใช้ คุณแยกโค้ดออกเป็นหลายชุดเพื่อให้สามารถใช้งานโค้ดแยกกันได้ ในทางกลับกันเลเยอร์สถาปัตยกรรมคือสิ่งประดิษฐ์เชิงตรรกะ เป็นไปได้ดีมาก (และทั่วไป) ที่จะมีหลายเลเยอร์ในชุดประกอบเดียวกัน

ในกรณีนี้เราจะมี Composition Root (เลเยอร์) และ Presentation Layer ในโปรเจ็กต์เว็บแอปพลิเคชันเดียวกัน (ในแอสเซมบลีเดียวกัน) และแม้ว่าที่อ้างอิงการชุมนุมการชุมนุมที่มี DAL ให้การนำเสนอชั้นยังคงไม่ได้อ้างอิงการเข้าถึงข้อมูลชั้น นี่คือความแตกต่างที่ยิ่งใหญ่

แน่นอนว่าเมื่อเราทำเช่นนี้เราจะสูญเสียความสามารถในการคอมไพเลอร์ในการตรวจสอบกฎสถาปัตยกรรมนี้ในเวลาคอมไพล์ แต่ก็ไม่น่าจะเป็นปัญหา กฎทางสถาปัตยกรรมส่วนใหญ่ไม่สามารถตรวจสอบได้โดยคอมไพเลอร์และมักจะมีบางอย่างที่เหมือนกับสามัญสำนึก และหากไม่มีสามัญสำนึกในทีมของคุณคุณสามารถใช้การตรวจทานโค้ดได้ตลอดเวลา (ซึ่งทุกทีมควรให้ IMO ทำเสมอ) คุณยังสามารถใช้เครื่องมือเช่น NDepend (ซึ่งเป็นเชิงพาณิชย์) ซึ่งช่วยในการยืนยันกฎสถาปัตยกรรมของคุณ เมื่อคุณรวม NDepend เข้ากับกระบวนการสร้างของคุณมันสามารถเตือนคุณได้เมื่อมีคนตรวจสอบโค้ดที่ละเมิดกฎสถาปัตยกรรมดังกล่าว

คุณสามารถอ่านการอภิปรายซับซ้อนมากขึ้นเกี่ยวกับวิธีการ Root องค์ประกอบทำงานในบทที่ 4 ของหนังสือของฉันพึ่งพาการฉีดหลักการปฏิบัติรูปแบบ


โครงการแยกต่างหากสำหรับ bootstrapping เป็นทางออกของฉันเนื่องจากเราไม่มี ndepend และฉันไม่เคยใช้มาก่อน ฉันจะดูมันแม้ว่ามันจะดูเหมือนเป็นวิธีที่ดีกว่าในการทำสิ่งที่ฉันพยายามจะทำเมื่อมีแอปพลิเคชั่นจบเพียง 1 ตัว
diegohb

1
ย่อหน้าสุดท้ายเป็นย่อหน้าที่ยอดเยี่ยมและกำลังเริ่มช่วยให้ฉันเปลี่ยนความคิดว่าฉันเข้มงวดแค่ไหนกับการเก็บเลเยอร์ในชุดประกอบแยกต่างหาก การมีเลเยอร์ตรรกะสองชั้นขึ้นไปในแอสเซมบลีเดียวนั้นใช้ได้ดีหากคุณใช้กระบวนการอื่น ๆ เกี่ยวกับการเขียนโค้ด (เช่นการตรวจสอบโค้ด) เพื่อให้แน่ใจว่าไม่มีการอ้างอิงคลาส DAL ในโค้ด UI ของคุณและในทางกลับกัน
BenM

6

หากฉันไม่ได้ใช้คอนเทนเนอร์ DI ฉันจะไม่ต้องอ้างอิงไลบรารี EntityFramework ในแอป MVC3 ของฉันมีเพียงชั้นธุรกิจของฉันเท่านั้นที่จะอ้างอิงเลเยอร์ DAL / Repo ของฉัน

คุณสามารถสร้างโปรเจ็กต์แยกชื่อ "DependencyResolver" ในโครงการนี้คุณต้องอ้างอิงไลบรารีทั้งหมดของคุณ

ตอนนี้ UI Layer ไม่จำเป็นต้องใช้ NHibernate / EF หรือไลบรารีอื่น ๆ ที่ไม่เกี่ยวข้องกับ UI ยกเว้น Castle Windsor ที่จะอ้างอิง

หากคุณต้องการซ่อน Castle Windsor และ DependencyResolver จากเลเยอร์ UI ของคุณคุณสามารถเขียน HttpModule ซึ่งเรียกสิ่งที่ลงทะเบียน IoC

ฉันมีเพียงตัวอย่างสำหรับ StructureMap:

public class DependencyRegistrarModule : IHttpModule
{
    private static bool _dependenciesRegistered;
    private static readonly object Lock = new object();

    public void Init(HttpApplication context)
    {
        context.BeginRequest += (sender, args) => EnsureDependenciesRegistered();
    }

    public void Dispose() { }

    private static void EnsureDependenciesRegistered()
    {
        if (!_dependenciesRegistered)
        {
            lock (Lock)
            {
                if (!_dependenciesRegistered)
                {
                    ObjectFactory.ResetDefaults();

                    // Register all you dependencies here
                    ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry()));

                    new InitiailizeDefaultFactories().Configure();
                    _dependenciesRegistered = true;
                }
            }
        }
    }
}

public class InitiailizeDefaultFactories
{
    public void Configure()
    {
        StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type);
          ...
    }
 }

DefaultControllerFactory ไม่ได้ใช้คอนเทนเนอร์ IoC โดยตรง แต่จะมอบหมายวิธีการคอนเทนเนอร์ IoC

public class StructureMapControllerFactory : DefaultControllerFactory
{
    public static Func<Type, object> GetController = type =>
    {
        throw new  InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!");
    };

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(requestContext, controllerType);
        }
        return GetController(controllerType) as Controller;
    }
}

GetControllerผู้รับมอบสิทธิ์ตั้งอยู่ใน Registry StructureMap (ในวินด์เซอร์ก็ควรจะติดตั้ง)


1
ฉันชอบสิ่งนี้มากกว่าสิ่งที่ฉันทำลงไปโมดูลนั้นยอดเยี่ยม ถ้าอย่างนั้นฉันจะโทรหา Container.Dispose () ได้ที่ไหน? ApplicationEnd หรือ EndRequest เหตุการณ์ภายในโมดูล ... ?
diegohb

1
@Steven เนื่องจาก Global.asax อยู่ใน MVC UI Layer ของคุณ HttpModule จะอยู่ในโครงการ DependencyResolver
Rookian

1
ประโยชน์เล็กน้อยคือไม่มีใครสามารถใช้คอนเทนเนอร์ IoC ใน UI ได้ กล่าวคือไม่มีใครสามารถใช้ IoC Container เป็นตัวระบุตำแหน่งบริการใน UI ได้
Rookian

1
นอกจากนี้ยังไม่อนุญาตให้นักพัฒนาใช้โค้ด DAL ในเลเยอร์ UI โดยไม่ได้ตั้งใจเนื่องจากไม่มีการอ้างอิงอย่างหนักไปยังแอสเซมบลีใน UI
diegohb

1
ฉันได้หาวิธีทำสิ่งเดียวกันโดยใช้ API การลงทะเบียนทั่วไปของ Bootstrapper โครงการ UI ของฉันอ้างถึง Bootstrapper ซึ่งเป็นโครงการแก้ปัญหาการอ้างอิงที่ฉันวางสายการลงทะเบียนและโครงการใน Core ของฉัน (สำหรับอินเทอร์เฟซ) แต่ไม่มีอะไรอื่นแม้แต่ DI Framework ของฉัน (SimpleInjector) ฉันใช้ OutputTo nuget เพื่อคัดลอก dlls ไปยังโฟลเดอร์ bin
diegohb

0
  • มีการพึ่งพา: ถ้าวัตถุเป็นอินสแตนซ์วัตถุอื่น
  • ไม่มีการพึ่งพา: หากวัตถุคาดว่าจะมีสิ่งที่เป็นนามธรรม (การฉีดตัวยึด, การฉีดวิธี ... )
  • การอ้างอิงแอสเซมบลี (การอ้างอิง dll, webservices .. ) เป็นอิสระจากแนวคิดการพึ่งพาเนื่องจากในการแก้ไขสิ่งที่เป็นนามธรรมและสามารถรวบรวมโค้ดได้เลเยอร์จะต้องอ้างอิงถึง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.