Search code examples
c#asp.net-web-apidomain-driven-designdata-access-layer

Should the domain layer receive a dependency DTO directly?


This may have been asked before, but I am accessing a dependency web service in my data access layer and I need to ask if I should be repackaging the DTOs returned from that service into my own DTOs? The UI layer is a WebAPI project with controllers and the domain and data access layers are separate C# projects. Is it correct to reference the dependency web services in each layer so that the dal, biz and domain layers all have the appropriate code references or should I be creating my own view of the DTOs returned from the web services in the DAL layer?


Solution

  • From a DDD point of view I do this in a similar way each time. I tend to use a Ports & Adapters type architecture over a classical layered architecture. Mainly because it allows me to easily reference my persistence layer via an interface wired up by IoC. This gives almost a 2-way reference, where not only can I access my persistence layer from my domain, but I can use my domain model in my persistence layer.

    What I do when accessing external web services is very similar. I set up a new adapter and put the interfaces for accessing the external service in my domain, which I then wire up using IoC. I can then access the external web service, using some DTOs that are usually auto-generated (or handmade) on my side (the client) and then mapping these to my domain objects.

    For example in a project I have where I'm doing geocoding (looking up postal codes into coordinates), in a folder called Geographical in my domain model, I have an interface called IGeocodingService:

    namespace Booking.Domain.Model.Geographical
    {
        /// <summary>
        /// The <see cref="IGeocodingService" /> interface.
        /// </summary>
        public interface IGeocodingService
        {
            /// <summary>
            /// Performs a postal code query.
            /// </summary>
            /// <param name="countryIsoCode">The country iso code.</param>
            /// <param name="postalCode">The postal code.</param>
            /// <returns>A geographic coordinate.</returns>
            Coordinate PostalCodeQuery(string countryIsoCode, string postalCode);
        }
    }
    

    In a separate project called Booking.Adapter.CloudMade I have a service called CloudMadeGeocodingService which inherits from IGeocodingService:

    namespace Booking.Adapter.CloudMade
    {
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Net.Http;
        using System.Text;
        using System.Threading.Tasks;
        using Booking.Domain.Model.Geographical;
    
        /// <summary>
        /// The <see cref="CloudMadeAdapter" /> class.
        /// </summary>
        public class CloudMadeGeocodingService : IGeocodingService
        {
            /// <summary>
            /// Performs a postal code query.
            /// </summary>
            /// <param name="countryIsoCode">The country iso code.</param>
            /// <param name="postalCode">The postal code.</param>
            /// <returns>
            /// A geographic coordinate.
            /// </returns>
            public Coordinate PostalCodeQuery(string countryIsoCode, string postalCode)
            {
                string country;
    
                switch (countryIsoCode)
                {
                    case "GB":
                        country = "UK";
                        break;
                    default:
                        country = null;
                        break;
                }
    
                if (country == null)
                {
                    return null;
                }
    
                using (HttpClient httpClient = new HttpClient())
                {
                    // TODO: Make call to CloudMade v2 geocoding API.
                }
    
                // TODO: Remove this.
                return null;
            }
        }
    }
    

    From my domain's point-of-view there are 2 big advantages for me to do things this way. The first is that the service wraps presents itself as some-sort-of-external-component and the second is that it takes and returns either native C# .NET types or my own domain model types. The implementation is hidden, in this case any DTOs if required (none really in this example) and the fact that the data is coming from a Web API web service is also hidden.