مدت ها به موضوع Interface Pollution فکر کردم. و وقتی به این عبارت فکر میکنم، اولین چیزی که به ذهنم میاد اینه که خب interface pollution معنی عامیانه ش این میشه که الکی الکی برای هر کاری همه جا interface بسازیم.
یه ذره که بیشتر تحقیق کردم دیدم در اصل این قضیه وقتی ایجاد میشه که به قول uncle bob شما اصطلاحا یه سری fat interface دارید. مثلا در نظر بگیرید که شما یه repository interface تعریف کردید، که برای entity های مختلف کاربرد داره و مثلا ۱۵، ۲۰ یا حتی بیشتر متد داره. حالا تصور کنید که یکی از سرویس هاتون که از همین repository استفاده میکنه و فقط ۲، ۳ تا متدش رو فراخوانی میکنه. حالا تصور کنید این سرویس به خاطر مسائل perofrmanceی یا هر چیز دیگه ای میخواد از یه repository دیگه استفاده کنه که مثلا in-memory باشه که latency خواندن و نوشتن داده ها در دیتابیس کمتر بشه. خب اون adapter جدید مجبور میشه الکی الکی متدهایی رو پیاده سازی کنه که لازمشون نداره. در حالیکه اگه interface ها به اندازه و کوچکتر تعریف میشدند، این مشکل نبود. این قضیه با رعایت کردن اصل چهارم SOLID یعنی Interface Segregation Principle قابل حل شدنه.
مثلا ما در پروژه adamak برای هر سرویس یک repository در نظر گرفتیم و با این کار سعی کردیم اصل چهارم SOLID رو رعایت کنیم.
اما حالا اگه در مورد golang بخوایم صحبت کنیم. نکات دیگه ای هم مهمه که خود من هم گاها رعایتشون نمیکنم. برای اینکه در کدهایی که با زبان Go مینویسید مشکل interface pollution اتفاق نیفته، میشه یه سری کارها کرد
اول اینکه از تعریف کردن interface تا حد امکان بپرهیزید. مگر اینکه پکیج شما واقعا generic-use functions داشته باشه. مثلا تابع Copy یکی از این توابع است. که پکیج io برای اون Writer و Reader را به صورت interface تعریف کرده.
دوم اینکه اگر کاربر یا همان کلاینت های پکیج شما به سطحی از «وارونگی کنترل» یا همان inversion of control احتیاج دارند بهتر است در scope خودشان این کار را انجام دهند. کاری که ما با نوشتن interface هایی مثل store در پروژه adamak انجام می دهیم.
دقت کنید این کار را در scope مرتبط با interactor مورد نظر انجام میدهیم (البته در پکیج contract) و هیچ پکیج خارجی ای را مجبور به expose کردن interface نمیکنیم.
همین قضیه برای تست نوشتن هم صدق می کند. گاها کلاینت ها از شما می خواهند یک interface را در پکیج خود expose کنید که کار mock کردن را برای آنها ساده تر کنید. که در اینجا هم می توان به آنها گفت که کلاینت در scope خودش یک interface تعریف کند که تنها متدهای مورد نیازش را در آن interface قرار می دهد.
type acknowledger interface {
Ack(sub string, id ...string) error
}
type mockClient struct{}
func (c *mockClient) Ack(sub string, id ...string) error {
return nil
}
var acker acknowledger = pubsub.New(...)
acker = &mockClient{} // in the test package
باز دقت کنید که تعریف interface در صورت لزوم توسط پکیج (کلاینت) استفاده کننده در همان scope تعریف می شود و پکیج شما لازم نیست interfaceی را expose کند.
البته باید در نظر بگیریم که adamak یک web service هست و شاید مثال زدن از آن برای این قضیه سخت باشه. چون عملا خیلی از پکیج های adamak خودشون کلاینت هستند و پکیج های عمومی کمتر توسعه داده میشه در اینطور سرویس ها.
در همین رابطه، این مقاله کوتاه رو هم حتما سعی کنید مطالعه کنید.