Up until now, we've only worked with a single assembly/project, which is a Web API project.
(Sidenote: A project in .NET will usually generate a single assembly. There are exceptions to this, but they are not that common. We will use the term project from now on. There are essentially just two types of projects: executables (which contain a Main() function), and class libraries. A Web API project in .NET is in fact just a class library with certain assemblies set as "References").
There are strong arguments for splitting the project up into more projects. The Single Responsibility Principle states that a given class/entity should have a single responsibility, and the same can be applied to projects as well. Having the actual Web API project "thin", e.g. separating the business logic in its own project, has some benefits:
- A single project will quickly become unmaintainable if the project gets large. Splitting it up makes maintenance easier.
- If for some reason we would decide later on to support another type of service, we could simply replace the Web API layer with a new layer, keeping the businless logic intact. This is however not directly applicable, since we may never support any other type of service, and YAGNI states that we shouldn't worry about this at all until we actually need it.
A Web API project will probably need some kind of data storage, either some SQL database, a NoSQL storage, or something else. It is very probable that there will be classes which will map to SQL tables or similar, we will refer to those classes as Entity classes. It might seem tempting to design the API such that methods will return instances of the entity classes directly. However, there are downsides to this:
- By exposing the entity classes to the outside world, we are exposing our database design to the API users. The design of the database may not match the model which we would like to use in our API.
- Entity classes may contain circular references to other entity classes, and this will cause trouble when they are serialized to XML/JSON.
The first point should probably be explained better. Assume we are working with the Course entity class, which will probably contain lots and lots of properties. When a client requests a list of courses, the client will usually only be interested in a few of the properties: the name of the course, the ID, etc. In those cases, it would be advisable to return just a subset of the properties/columns defined in the Course class/table. It would be a very bad design to return a list of Course objects, but only with those properties filled out we actually use. This would imply that those properties might somehow become accessible later, or it could indicate that those values are missing from the data store.
Another reason is when a given entity class doesn't contain all necessary fields, due to the design of the data model. Example: assume we've got two classes: CourseTemplate (which contains general information about a given course), and a Course class, which contains information about a given instance of a course, i.e. a course taught in a given semester by a given teacher etc. Our data model will probably be set up such that we don't duplicate data, and in that case, the name of the course will probably be stored in our CourseTemplate table. However, when we want to get a list of courses (course instances), we will probably want to include the name of the course!
A third reason is when the user is creating an instance of some class via our API. Some of the properties in our entity class are perhaps calculated or fetched from somewhere else. For example, it is not unheard of to store the date of creation as one property in a database row. However, this is a value which should be supplied by the server, but NOT by the client. It is therefore better to have a separate class which contains exactly the properties the client is allowed to supply, but nothing more, and let the "Create" method accept an instance of that class as a parameter.
A final case is when the database table contains data we don't want to expose to the client, but need to store for various reasons. The date when the entity was created could be an example of such data, which may not always be wise to expose to the client.
It should be pretty clear that separating these two concepts: entity classes, and model classes exposed to the API users, is the way to go.
The model classes exposed to the outside world go by various names. The term Model class could be confused with our data model (entity classes) but is sometimes used nonetheless. The term Data Transfer Object (or DTO) is sometimes used as well, although some say a DTO should strictly be used internally, when moving data from one module to another. Finally, the term ViewModel is also used sometimes, although a ViewModel usually contains some behaviour.
The jury is still out on what naming convention will be accepted. We will use a DTO class for simple classes returned from the API, but a ViewModel class for data passed into the API, since we may want to add validation logic to those classes (see later).
One possible setup of our projects is as follows:
- Web API project - takes care of the HTTP communications, doesn't contain any business logic.
- Models project - contains our DTO and ViewModel classes, which are exposed by the service.
- Service project - contains the business logic.
- Tests project - contains the unit tests (see later). Could possibly be split up into more projects, one for testing the Services, another for testing the models etc.
- Entities project - contains our data model (i.e. classes which map to database tables).
Note that there could be variations to this, depending on the project and the requirements.
This blog post outlines the necessary steps to add a project to a solution using the dotnet tool. When using Visual Studio, the steps are a bit easier.