從事軟體開發,特別是從Web領域開始的developers,一定因為各框架普遍提供MVC架構的關係而大致了解MVC的功能區分。
然而,當專案大了起來,很快就會發現如何讓MVC分工,比想像中困難:
* 某些code到底該放在C還是M?
* 修改V內的某些code會直接影響到business logic的運作與安全…這不應該吧?
很顯然地,對軟體架構的知識只擁有MVC是不夠的。
我舉一個更殘酷的例子。我在Reddit曾經和國外的工程師討論MVC的問題:
What exactly is ‘model’ in MVC?
最高分的回答如下,謝謝Nephand:
I would say think of the ‘model’ more as your application – it’s not a single thing. The ‘view’ and ‘controller’ are just a channel of communication into your application’s behaviours.
Another word to replace ‘model’ might be ‘domain’ – see Domain Driven Design. Your application domain might need to deal with emails, or SMS but such things aren’t specific to your domain – generally speaking – so they’re services that your model is aware of (even if it’s just dispatching an event that your services listen for). They’re channels of communication into and out of your domain.
Your web interface talks to the application domain through a view and controller. Yet imagine you want to offer an API – this has no need of the View [simplistically put]. Also, your application might talk to the outside world through an SMS Service, or an Email Service and so on.
Personally, I like Hexagonal architecture as a way of conceptualising this.
Your ‘model’/’domain’/’core application’ then might have, for example, a persistence/database layer which creates a ‘seam’ between your application’s logic and the actual data storage format. This can be great, it translates out complex objects into a simple data format (SQL Rows…Mongo Documents…Redis)
You mention the idea of ‘one model’ and ‘one table’ – and you’re right to say this part is confusing. The ‘model == table’ approach has plenty of benefits, in that it’s pretty simple and easy to follow – but it can be limiting.
Having a ‘seam’ between your model and the data store becomes useful as it grows more complex. Within your ‘model’ you might have one ‘god object’ that serves as a root for multiple sub-objects. Each part can encapsulate behaviours that its containing object doesn’t need to know about – but all of that complexity still constitutes just one table row.
Domain Driven Design (as a primary voice but not the only one) differentiates Entity and Value Object [the classic Value Object being Money ].
The Entity might represent what you’re thinking of as a ‘row’ in your table (in the most general sense) – it has something that makes you want to be able to track its lifespan and differentiate it from others inside your application (e.g. a unique primary key ‘ID’).
Quick and dirty example: A User. A User would/could be an Entity, with a unique identifier. A User might have an email address. The concept of an ‘Email Address’ is not unique to a User, and so it might be a good candidate for being a Value Object that can be used by anything in your application domain/model. So when you later need to give another Entity (say a Company) an email address, you can re-use that same value object and all of the behaviours you might want for all EmailAddresses in your domain.
Lets say your business requires a hatred of hotmail users, and will never consider @hotmail.com a valid email address. You can then encapsulate such a rule into your EmailAddress object’s constructor [other approaches are preferred perhaps but I did say quick and dirty].
Whether you use EmailAddress in the context of a User or a Company, it will always throw an Exception / fail elegantly.
This is not [typically] intended to validate user input, but to ensure that your model/domain is meeting your hotmail hatred requirement by not allowing an EmailAddress value object for “*@hotmail.com” to be created. Whether that is passed in from a web form, over an API, or parsed in from an SMS interface shouldn’t matter to your core logic – you’re just saying ‘never allow this to happen even if it makes it past input validation somehow’.
This comes back to your concern about having a complex model separated into different models and in turn tables. That is where different strategies for mapping your ‘model’ to your data store come in. The Data Mapper is arguably the most flexible – and in turn potentially complicated – to implement. Patterns of Enterprise Application Architecture in combination with the Evans book Domain Driven Design can go a long way to help you understand these ideas, even if you water them down to ‘keep it simple [stupid]’.
Basically your User table (VALUES user_id, email_address) would be updated with the $user->id and $user->emailAddress->getAddress() values to store. A Company would look similar, $company->id, $company->emailAddress->getAddress().
The great part about all of this is that you can start to break up and refactor your god objects into smaller parts, that can be persisted separately or not. While your god object might still serve as a ‘root’ object, you will hopefully have been able to split its behaviours off into smaller chunks that can be tested easily, persisted easily, and even re-used where needed by other parts of your model.
This is all well and good, but don’t over-engineer things. If it’s just a string value, then just let it be a string value. Until you arrive at a behaviour requirement that makes it useful to implement an abstraction then it’s probably just bloat.
Obligatory warning This is a massive over-simplification of some more interesting concepts than I’d be able to explain – both due to lack of experience and time. Apologies to the knowledgeable if it’s a bad explanation – and to everyone else if it’s especially rambling, I don’t have time to proof read.
Quick Links for getting to grips with the model: – Unbreakable Domain Models – good quick intro. – Domain Driven Design – Patterns of Enterprise Application Architecture – Command Query Request Separation
為了回答我關於MVC的問題,他引用了大量的專有名詞…我完全看不懂,但大致知道是關於Domain Driven Design的東西。看起來這些概念已經是國外討論軟體架構的必備知識了。
而這些,在台灣很少聽人提到。
關於DDD其實我早有耳聞。Rails之父DHH在
The five programming books that meant most to me
曾經提到一本關於Domain Driven Design的書:由Eric Evans撰寫的Domain-Driven Design: Tackling Complexity in the Heart of Software
我翻過之後實在是..很厚重,再加上又是原文,唸不下去。
今天在網路上找到此書由其他作家精簡過之後,接著翻譯為簡體中文的版本
我還沒有心得可以分享,因為我還沒有消化完。
但是目前翻閱了三分之一,確實非常精彩。
開卷有益啊朋友,有空翻一翻吧。