Reputation: 45
Let's say I have an ASP.NET Core Web API application, and one of my action methods receives IEnumerable<AddressModel> addresses
, where AddressModel
looks like:
public class AddressModel
{
public string Street { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
I'd like to use it to construct a more complex message object and to send it via MassTransit - Addresses
will be a nested property and I'll set more fields:
public interface ICreateContact
{
ContactTypeEnum Type { get; }
List<IAddress> Addresses { get; }
}
public interface IAddress
{
string Street { get; }
string ZipCode { get; }
string City { get; }
string Country { get; }
}
So, how to create such message in the most convenient readable way? I see a few options, but all of them have drawbacks:
await _messageBus.Send<ICreateContact>(new {
Type = ContactTypeEnum.Single,
Addresses = addresses.Select(a => new
{
Street = a.Street,
ZipCode = a.ZipCode,
City = a.City,
Country = a.Country
})
});
Will work, but I don't want to write a lot of code to assign each property and I can't use Automapper, because there're no setters in ICreateContact/IAddress
.
public class CreateContact
{
public ContactTypeEnum Type { get; set; }
public List<Address> Addresses { get; set; }
public class Address
{
public string Street { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
}
var command = new CreateContact
{
Type = ContactTypeEnum.Single,
Addresses = addresses.Select(a => _mapper.Map<CreateContact.Address>(a)).ToList()
};
await _messageBus.Send<ICreateContact>(command);
Looks better, but what if I want it implement ICreateContact/IAddress
, so compiler will tell if I construct the message incorrectly? I can't do that, because if I write CreateContact : ICreateContact
, my Addresses
field must be of type List<IAddress>
(even if I make Address
implement IAddress
).
So to summarize my questions:
Upvotes: 4
Views: 2879
Reputation: 33388
First, you don't need an immediate class, you can just use another nested anonymous type to initialize the address list. MassTransit's message initializer does most mapping between types by convention, including type conversions (string to int, date, etc.).
Since all the property names match, you could easily use:
await _messageBus.Send<ICreateContact>(new {
Type = ContactTypeEnum.Single,
Addresses = addresses
});
And it would initial the address list with elements from the input addresses.
Second, your question about strongly typed classes for producers? It depends. I definitely wouldn't share those concrete types outside of the message producer. If you want that level of property type/name validation, you can do it. It's just more classes to manage in your project. I've seen it done in some teams that needed that level of direction for engineers, but more often I see teams happy to just let it go and guard contract changes at the repository level.
Third, you can make the concrete type have the correct element type (IAddress) and add the Address concrete types to it. That eliminates the array type issue, and still allows you to use an Address concrete type that implements IAddress.
Fourth, seriously, don't rename properties! If it's a mistake, fix it globally using a tool and grep to find any usages across repositories. But once it's in production, renaming it can break things. In that case, add a new property (correctly spelled, whatever) and updated producers to send both eventually, and once systems are upgraded across the board, deprecate the original property.
There are more details on what MassTransit support with message initializers in the documentation.
Upvotes: 5