I'm using NServiceBus and I need to know how many clients are subscribed to a specific message type (even better the names of the subscribers). I'm talking in a pub\sub scenario.
Is it possible to get this information in NServiceBus?
Thx
You can pull this right out of your subscription storage. Either a query to the database or a .GetAllMessages() on the queue will get you a count and the subscribers address. If you are looking to do this in code, you could write a handler for the subscription message and count them up that way.
I have used ISubscriptionStorage with success.
public class SubscribersForMessageHandler :
IHandleMessages<SubscribersForMessageRequest>
{
public ISubscriptionStorage Storage { get; set; }
public IBus Bus { get; set; }
public void Handle(SubscribersForMessageRequest message)
{
Bus.Reply<SubscribersForMessageResponse>(m=>
{
m.SagaId = message.SagaId;
m.MessageType = message.MessageType;
m.SubscriberEndpoints = GetSubscribersForMessage(message.MessageType);
});
}
private List<string> GetSubscribersForMessage(string type)
{
return Storage.GetSubscribersForMessage(
new List<string> { type }).ToList();
}
}
Related
I have an entity type:
public class Log
{
public int Id { get; set; }
public string Action { get; set; }
public string Message { get; set; }
}
And my Index:
public class LogIndex : AbstractIndexCreationTask<Log>
{
public LogIndex()
{
Map = xs => from x in xs
select new
{
x.Id,
x.Action,
x.Message
};
}
}
Then I can use them to store logs and I can use context.Query<Log, LogIndex>().Where(x => x.Action== "GetString").ToList() to get logs.
And then I try to use Commands to query my logs:
QueryResult queryResult = context.Advanced.DocumentStore.DatabaseCommands.Query("LogIndex", new IndexQuery
{
Query = "Action:(GetString)"
});
Log log = queryResult.Results.First().ToObject<Log>();
My problem is:
The log which is returned by ToObject<Log>() lose it's Id property's value(it is 0). But it's Action and Message property's value are not lost..
Is using RavenJObject.ToObject<T>() the right way to get query result(entities) ? If it is, what's wrong with my code? If it is not, which is the right way?
No, it isn't the proper way to go about it.
To start with, you are using very low level API, and should make use of the session for this.
If you'll use the session, it will take care of setting the ID properly.
I have implemented a my connector using nservice bus saga. Below is the code
public class ClientSaga : Saga<ClientSagaState>,
IAmStartedByMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientContactChangeMessage>,
IAmStartedByMessages<ClientPictureChangeMessage>,
IHandleTimeout<ClientSagaState>
{
[SetterProperty]
public IClientContactChangeDb ClientContactChangeDb{get;set;}
[SetterProperty]
public IBusRefTranslator BusRefTranslator{get;set;}
public void Handle(ClientContactChangeMessage message)
{
var state=this.Data;
//Some handling logic
//Check if client is not in database then store the state
state.ClientContactChange=message;
state.ClientRef =message.ClientRef;
//if client is in the data base then
MarkAsComplete();
}
public void Handle(ClientChangeMessage message)
{
var state=this.data;
//Update or create the client depending on the situation
//check for dependencies
if(state.ClientContactChange !=null)
{
//Handle the contact change
}
else
{
state.ClientChangeMessage=message;
state.ClientRef=message.ClientRef;
}
}
public void Handle(ClientPictureChangeMessage message)
{
var state=this.Data;
//If the client is there then update the picture else store in saga
state.ClientPictureChangeMessage =message;
state.ClientRef=message.ClientRef;
}
}
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientContactChangeMessage>(s => s.ClientRef, m => m.ClientRef);
ConfigureMapping<ClientPictureChangeMessage>(s => s.ClientRef, m => m.ClientRef);
ConfigureMapping<ClientChangeMessage>(s => s.ClienttnRef, m => m.Id);
}
}
public class ClientSagaState: IContainSagaData
{
//i dont need these three fields
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
// the fields which i needed
public Guid ClientRef {gee; set;}
public ClientChangeMessage ClientChange {get;set;}
public ClientContactChange ClientContactChange {get;set;}
public ClientPictureChangeMessage ClientPictureChangeMessage {get;set;}
}
Now in my connector a client cannot be created w/o client contact change message being present.
Case when saga fails:
When i send the the client picture message first it creates a new
saga and stores it.
Then i send a client change message it creates another saga and
stores it i.e does not find the saga created by the client picture
message
Then i send the client contact change message it somehow finds the
saga created by client picture change but now cannot find the staff.
I can't make out why this is happening.
Case when saga succeeds:
When i send the client change message first it creates the saga.
Then i send the client contact change message it finds the saga
and executes fine.
Can anyone please explain why this behaviour is happening.
Please let me know if more information is needed.
Thanks
UPDATE
On checking my code again, i found the cause of this . My ClientChangeMessage was also inheriting from IContainSaga data(something which i was trying out but had forgotten to remove). After removing the inheritance link everything was working fine. (Head hanging in shame)
In all your handlers, you need to set the ClientRef on the Saga Data.
So, you would have:
public void Handle(ClientContactChangeMessage message)
{
Data.ClientRef = message.ClientRef
...
}
As any of these messages can start the saga, you'll need to set this value in your saga state. When other messages come in, then it will be co-rrelated by this id as there is already an instance of the saga with this Id.
To refer to your saga state variables, use Data. intead of this.
We have a Web app (ASP.NET/C#) with SQL Server backend. We use ServiceStack OrmLite as our POCO Micro ORM. We would now like to extend a part of our app to cache frequently-read data (mainly a collection of POCO objects as values, with numeric keys). But I'm not sure how to go about integrating a simple caching solution (in-memory or Redis based) that works seamlessly with OrmLite and MSSQL as the Master database.
I've read about the ServiceStack Redis Client, MemoryCacheClient and Multi nested database connections (OrmLiteConnectionFactory), but I couldn't find any examples, tutorial or code samples to learn more about implementing caching that works with OrmLite.
Any suggestions or links will be helpful and much appreciated.
I use this extension to help simplify the integration between the db and the cache.
public static class ICacheClientExtensions
{
public static T ToResultUsingCache<T>(this ICacheClient cache, string cacheKey, Func<T> fn, int hours = 1) where T : class
{
var cacheResult = cache.Get<T>(cacheKey);
if (cacheResult != null)
{
return cacheResult;
}
var result = fn();
if (result == null) return null;
cache.Set(cacheKey, result, TimeSpan.FromHours(hours));
return result;
}
}
public class MyService : Service
{
public Data Get(GetData request)
{
var key = UrnId.Create<Data>(request.Id);
Func<Data> fn = () => Db.GetData(request.Id);
return Cache.ToResultUsingCache(key, fn);
}
[Route("/data/{id}")]
public class GetData: IReturn<Data>
{
public int Id{ get; set; }
}
}
You'd need to implement the caching logic yourself, but it's not much work - here's a pseudocode example:
public class QueryObject
{
public DateTime? StartDate { get; set; }
public string SomeString { get; set; }
}
public class Foo
{
public DateTime DateTime { get; set; }
public string Name { get; set; }
}
public class FooResponse
{
public List<Dto> Data { get; set; }
}
public FooResponse GetFooData(QueryObject queryObject)
{
using (var dbConn = connectionFactory.OpenDbConnection())
using (var cache = redisClientsManager.GetCacheClient())
{
var cacheKey = string.Format("fooQuery:{0}", queryObject.GetHashCode()); //insert your own logic for generating a cache key here
var response = cache.Get<Response>(cacheKey);
//return cached result
if (response != null) return response;
//not cached - hit the DB and cache the result
response = new FooResponse()
{
Data =
dbConn.Select<Foo>(
x => x.DateTime > queryObject.StartDate.Value && x.Name.StartsWith(queryObject.SomeString)).ToList()
};
cache.Add(cacheKey, response, DateTime.Now.AddMinutes(15)); //the next time we get the same query in the next 15 mins will return cached result
return response;
}
}
Have you checked Service stack caching wiki. It gives detailed info about caching. Now in your case from the details you are providing I can say that you can go for any kind of caching. As of now it will not make any difference.
PS: A piece of advice caching should be done when there is no option or the only thing pending in application. Because it comes with it's own problem is invalidating caching, managing and all that. So, if you application is not too big, just leave it for now.
I am trying to create a static index for the following sample class:
public class Board {
...other assorted fields
List<dynamic> Messages {get; set;}
internal Board() {Messages = new List<dynamic>();}
}
The index is to filter boards which have messages which are a older than a certain date. The aim is to perform an "update" operation on messages which are due today, update their content, and persist them back. The index is needed to avoid traversing all the messages for a board for all clients as that may be computationally expensive. Messages is a list of message types which inherit from a base class which contains a property ExpiryDate.
Trying to create an index like follows results in an "An expression tree may not contain a
dynamic operation" error. I know that the dynamic type does not play well with Linq queries hence the need to use LuceneQueries instead of Query() in RavenDB. Is there any way to make this index work with dynamic properties? Thanks!
public class ScanBoardMessagesIndex : AbstractIndexCreationTask<Board>
{
public ScanBoardMessagesIndex () {
Map = boards => from board in boards
where board.Messages.Any(msg => ((MessageItem) msg).ExpiryDate <= DateTime.UtcNow.Date)
select board;
}
}
EDIT:
I ran into a raven serialization issue because the metadata clr-type of existing Board documents was set to a class namespace which was not valid anymore. I am doing a migration project so I went ahead and first issued a patch to change the metadata clr-type of the existing documents before migrating them to the new data structure which uses a base/abstract class for list of Messages instead of type dynamic.
A Map/Reduce index seems more appropriate for the given requirements. Effectively, you want to be able to query boards by the oldest expiry date of messages in the board. This is an aggregating operation, exactly what Map/Reduce was designed to solve. Also, using a base class for messages will allow you to define the index without resorting to the lower level IndexDefinition:
public class Message
{
public DateTime ExpiryDate { get; set; }
}
public class Board
{
public string Id { get; set; }
public List<Message> Messages { get; set; }
}
public class OldestExpiryDateMessageInBoard : AbstractIndexCreationTask<Board, OldestExpiryDateMessageInBoard.Result>
{
class Result
{
public string BoardId { get; set; }
public DateTime OldestExpiryDate { get; set; }
}
public OldestExpiryDateMessageInBoard()
{
this.Map = boards => from board in boards
from message in board.Messages
select new
{
BoardId = board.Id,
OldestExpiryDate = message.ExpiryDate
};
this.Reduce = results => from result in results
group result by result.BoardId into g
select new
{
BoardId = g.Key,
OldestExpiryDate = g.Min(x => x.OldestExpiryDate)
};
}
}
You can then query this index with Lucene syntax.
I have 2 different sagas (I mean saga types) that handle the same message.
public class AttachMessageToBugSaga : TpSaga<AttachMessageToBugSagaData>, IAmStartedByMessages<MessageIsNotAttached>, IHandleMessages<MessageAttachedToGeneralMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<MessageAttachedToGeneralMessage>(
saga => saga.Id,
message => message.SagaId
);
}
public void Handle(MessageIsNotAttachedToBug message)
{
Send(new AttachMessageToGeneralCommand { MessageId = 66, GeneralId = 13 });
}
public void Handle(MessageAttachedToGeneralMessage message)
{
//do some stuf fhere
}
}
public class AttachMessageToBugSagaData : IContainSagaData
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
}
public class AttachMessageToRequestSaga : TpSaga<AttachMessageToRequestSagaData>, IAmStartedByMessages<MessageIsNotAttachedToRequest>, IHandleMessages<MessageAttachedToGeneralMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<MessageAttachedToGeneralMessage>(
saga => saga.Id,
message => message.SagaId
);
}
public void Handle(MessageIsNotAttachedMessageToRequest message)
{
//do some stuff here
}
public void Handle(MessageAttachedToGeneralMessage message)
{
//do some stuff here
}
}
public class AttachMessageToRequestSagaData : IContainSagaData
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
}
When I run the sample I get an exception :
System.InvalidCastException: Unable to cast object of type 'MyCustomPlugin.AttachMessageToGeneralSagaData' to type 'MyCustomPlugin.AttachMessageToRequestSagaData'.
I understand why it happens, but I still need some workaround. I tried to implement my own IFindSagas class :
public class SagaFinder : IFindSagas<AttachMessageToGeneralSagaData>.Using<MessageAttachedToGeneralMessage>,
IFindSagas<AttachMessageToRequestSagaData>.Using<MessageAttachedToGeneralMessage>,
IFindSagas<AttachMessageToRequestSagaData>.Using<MessageIsNotAttachedToRequest>,
IFindSagas<AttachMessageToRequestSagaData>.Using<MessageIsNotAttachedToBug>
{
AttachMessageToGeneralSagaData IFindSagas<AttachMessageToGeneralSagaData>.Using<MessageAttachedToGeneralMessage>.FindBy(MessageAttachedToGeneralMessage message)
{
return ObjectFactory.GetInstance<AttachMessageToGeneralSagaData>();
}
AttachMessageToRequestSagaData IFindSagas<AttachMessageToRequestSagaData>.Using<MessageAttachedToGeneralMessage>.FindBy(MessageAttachedToGeneralMessage message)
{
return ObjectFactory.GetInstance<AttachMessageToRequestSagaData>();
}
public AttachMessageToRequestSagaData FindBy(MessageIsNotAttachedToRequest message)
{
return new AttachMessageToRequestSagaData();
}
public AttachMessageToRequestSagaData FindBy(MessageIsNotAttachedToBug message)
{
return new AttachMessageToRequestSagaData();
}
}
But I do not get into my finders for "MessageAttachedToGeneralMessage".
Please tell me if there is some other workaround, or how to make this example working.
I'm not sure that having more than one Saga within the same process boundary works very well - at least, I've had problems with it too. It's probably better (in general) to have Sagas separated into two different processes anyway, because otherwise it would cause a lot of locking and potentially deadlocks on your saga storage.
Is your message that is handled by 2 Sagas Sent or Published? If it's published (or can be made to), it would be easy to separate the Sagas into two separate assemblies. Just be sure to manually call Bus.Subscribe() for the message type in each Saga, since Sagas don't auto-subscribe to messages listed in the app.config.
If your message is Sent, and there's nothing you can do to change it, then create a central handler for your existing message type that either Publishes a second message type to go to both Sagas, or Sends two separate messages to each saga.
Finally (after digging into the source code) I've found the solution. It seems the only way is to implement my own SagaPersister, where I can do anything I want.
Default implementation in NserviceBus of InMemorySagaPersister has the following code :
T ISagaPersister.Get<T>(Guid sagaId)
{
ISagaEntity result;
data.TryGetValue(sagaId, out result);
return (T)result;
}
And exception occurs while casting .