pardahlman / RawRabbit

A modern .NET framework for communication over RabbitMq
MIT License
746 stars 144 forks source link

RawRabbit not Publishing, Requesting complex objects #369

Open BeePM opened 6 years ago

BeePM commented 6 years ago

Hi guys,

I developed a microservice architecture based project and the main communication tool between microservices is RabbitMQ. I'm using RawRabbit library to handle all communication logic.

Everything is working as expected and pretty fast, however, I've encountered a couple of things that I found really weird and I would like to discuss it here, because maybe I'm doing something wrong.

The problem is with complex objects. When I'm sending, receiving classes with primitive types inside, everything is ok, however, the fun starts here:

 public class Employee : DomainBase<int>, IValidatable
    {
        public string Name {get;set;}

        public int Version {get;set;}

        [Required]
        public Guid EmployeeId {get;set;}

        public bool IsSynchronized {get;set;}

        [Required]
        public string Username {get;set;}

        public string Email {get;set;}

        public int? PositionId {get;set;}

        public virtual Position Position {get;set;}

        public int? ProjectId {get;set;}

        public virtual Project Project {get;set;}

        public int? LocationId {get;set;}

        public virtual Location Location {get;set;}

        public string TelephoneNumber {get;set;}

        public string TotalExperienceMonths {get;set;}

        public string TotalExperienceYears {get;set;}

        public int? BandId {get;set;}

        public virtual Band Band {get;set;}

        public string SmallAvatar {get;set;}

        public string ProfileAvatar {get;set;}

        public DateTime? JoiningDate {get;set;}

        public int? ReportingManagerId {get;set;}

        public virtual Employee ReportingManager {get;set;}

        public virtual ICollection<EmployeeNote> Notes {get;set;} = new List<EmployeeNote>();
        public virtual ICollection<Salary> SalaryHistory {get;set;} = new List<Salary>();
        public virtual ICollection<EmployeeHistoryItem> HistoryRecords {get;set;} = new List<EmployeeHistoryItem>();

        public bool IsValid
        {
            get
            {
                return !string.IsNullOrWhiteSpace(Username) && EmployeeId != default(Guid);
            }
        }

        public override bool Equals(object obj)
        {
            if(object.ReferenceEquals(obj, null)) return false;
            if(!(obj is Employee)) return false;
            if(object.ReferenceEquals(this, obj)) return true;
            if(this.GetType() != obj.GetType()) return false;
            Employee employee = obj as Employee;
            return employee.Name == Name && employee.Email == Email && employee.Username == Username
                && employee.TelephoneNumber == TelephoneNumber && employee.TotalExperienceMonths == TotalExperienceMonths
                && employee.TotalExperienceYears == TotalExperienceYears && employee.SmallAvatar == SmallAvatar
                && employee.ProfileAvatar == ProfileAvatar && employee.JoiningDate == JoiningDate
                && EqualsInternal(employee);
        }

        private bool EqualsInternal(Employee employee)
        {
            if(employee.ReportingManager != null && ReportingManager != null && ReportingManager.Name != employee.ReportingManager.Name)
            {
                return false;
            }

            if(employee.Band != null && Band != null && Band.Name != employee.Band.Name)
            {
                return false;
            }

            if(employee.Location != null && Location != null && Location.Name != employee.Location.Name)
            {
                return false;
            }

            if(employee.Position != null && Position != null && Position.Name != employee.Position.Name)
            {
                return false;
            }

            if(employee.Project != null && Project != null && Project.Name != employee.Project.Name)
            {
                return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return Name.GetHashCode() + Email.GetHashCode() + TelephoneNumber.GetHashCode()
                + TotalExperienceMonths.GetHashCode() + TotalExperienceYears.GetHashCode() + SmallAvatar.GetHashCode()
                + ProfileAvatar.GetHashCode() + JoiningDate.GetHashCode();
        }

        public override string ToString()
        {
            PropertyInfo[] properties = this.GetType().GetProperties();
            var sb = new StringBuilder();
            foreach(var info in properties)
            {
                var value = info.GetValue(this, null) ?? "null";
                sb.AppendLine($"{info.Name}: {value.ToString()}");
            }

            return sb.ToString();
        }

This is my domain class, which I would like to receive from another microservice via RabbitMQ, but for some reason I'm not able to receive anything. I tried to modify this class and I left only primitive types + strings and everything worked.

I also had a similar problem with my Logging service. I introduced a logging system based on RabbitMQ by sending Log commands over the network and I had a class for that like:

public class LogCommand {
    public string Message {get;set;}
    public object[] Parameters {get;set;}
}

So the problem was, when I created LogCommand and I set the Message property and then ran _bus.PublishAsync, rabbit was able to send it over the network, however, when I set Parameters property and then ran _bus.PublishAsync, the message never arrives anywhere.

Any thoughts?

tafs7 commented 6 years ago

@BeePM I think you have to take a step back as I think you're making a mistake when it comes to DDD.

IMHO, you should not be sending domain objects across network boundaries to other microservices. You should encapsulate your domain objects and keep them within your bounded context (your microservice). I would recommend instead that you create contracts that are specifically used for messaging and map from your domain aggregates to those message contracts.

This will ensure that your internal domain models can evolve without breaking the message contract with other services.

pardahlman commented 6 years ago

Hello @BeePM,

If you want to troubleshoot this, I would suggest looking at the RabbitMQ management tool and see if the message is published at all. Here the RawRabbit logs can be useful, too. I'm guessing that there could be some sort of serialization problem.

Once you have verified that the message is published, you could check the logs on the subscriber and see if there are any exceptions there. You could also create a custom queue that receives the payload and copy the json and check if it deserializes to the target type on the subscriber side.