Im currently working with Dependency injection in my services and im curious what are the best practices in mongoDB.
My class use IMongoDatabase which is DI in constructor, but VS warns me that "This interface is not quaranteed to remain stable. Implementors should use MongoDatabaseBase.
I have seen both Client and Collection being passed in constructor in various examples.
At the moment i create Client, GetDatabase and then register it as a singleton for my other services to use.
I am using .Net 6.0
Nugets
MongoDB.Driver(2.15.1)
Microsoft.Extensions.Hosting
Main:
string connectionString = "mongodb://localhost:27017/"
string databaseName = "DBName"
MongoClient client = new MongoClient(connectionString);
IMongoDatabase mongoDb = client.GetDatabase(databaseName);
services.AddSingleton<IMongoDatabase>(mongoDb);
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddSingleton<IMongoDatabase>(mongoDb);
services.AddHostedService<MyServiceUsingMongo>();
})
.Build()
.Run();
Class:
private IMongoDatabase _db;
public MyServiceUsingMongo(IMongoDatabase db)
{
_db = db;
}
Thanx for any tips.
Related
I am new to Spring Aop, but I have case to implement AOP advice for a mongo db call(monog db update). I am trying in different way but getting 'Point cut not well formed' error or 'warning no match for this type name: arg string [Xlint:invalidAbsoluteTypeName]'(even if I give absolute name of the argument). Anyone can help on this as how to inject advice for mongo db update call?
#Aspect
#Component
public class DBStatsLoggerAspect {
private static final Logger log = LoggerFactory
.getLogger(DBStatsLoggerAspect.class);
private static final Document reqStatsCmdBson = new Document(
"getLastRequestStatistics", 1);
private DbCallback<Document> requestStatsDbCallback = new DbCallback<Document>() {
#Override
public Document doInDB(MongoDatabase db) throws MongoException,
DataAccessException {
return db.runCommand(reqStatsCmdBson);
}
};
#After("execution( public * com.mongodb.client.MongoCollection.*(..)) && args(org.bson.conversions.Bson.filter,..)")
public void requestStatsLoggerAdvice(JoinPoint joinPoint) {
MongoTemplate mongoTemplate = (MongoTemplate) joinPoint.getTarget();
log.info(mongoTemplate.execute(requestStatsDbCallback).toJson());
}
}
Actual db call method where I need to inject advice:(filter, updatePart all are org.bson.conversions.Bson data type) and here 'collection' is com.mongodb.client.MongoCollection.collection
Document result = collection.findOneAndUpdate(filter, updatePart, new FindOneAndUpdateOptions().upsert(false));
I am not a Spring or MongoDB user, just an AOP expert. But from what I see I am wondering:
You are intercepting execution(public * com.mongodb.client.MongoCollection.*(..)), so joinPoint.getTarget() is a MongoCollection type. Why do you think you can cast it to MongoTemplate? That would only work if your MongoCollection happened to be a MongoTemplate subclass. To me this looks like a bug.
Class MongoCollection is not a Spring component but a third-party class. Spring AOP can only intercept Spring component calls by means of creating dynamic proxies for those components and adding aspect interceptors to said proxies. so no matter how correct or incorrect your pointcut, it should never trigger.
What you can do instead is switch from Spring AOP to full-blown AspectJ. The standard way to do this is to activate AspectJ load-time weaving (LTW).
In spring boot if we want to connect to mongodb, we can create a configuration file for mongodb or writing datasource in application.properties
I am following the second way
For me, I am gettint this error
"Timeout while receiving message; nested exception is com.mongodb.MongoSocketReadTimeoutException: Timeout while receiving message
.
spring.data.mongodb.uri = mongodb://mongodb0.example.com:27017/admin
I am gettint this error If I am not using my app for 6/7 hours and after that If I try to hit any controller to retrieve data from Mongodb. After 1/2 try I am able to get
Question - Is it the normal behavior of mongodb?
So, in my case it is closing the socket after some particular hours
I read some blogs where it was written you can give socket-keep-alive, so the connection pool will not close
In spring boot mongodb connection, we can pass options in uri like
spring.data.mongodb.uri = mongodb://mongodb0.example.com:27017/admin/?replicaSet=test&connectTimeoutMS=300000
So, I want to give socket-keep-alive options for spring.data.mongodb.uri like replicaset here.
I searched the official site, but can't able to find any
You can achieve this by providing a MongoClientOptions bean. Spring Data's MongoAutoConfiguration will pick this MongoClientOptions bean up and use it further on:
#Bean
public MongoClientOptions mongoClientOptions() {
return MongoClientOptions.builder()
.socketKeepAlive(true)
.build();
}
Also note that the socket-keep-alive option is deprecated (and defaulted to true) since mongo-driver version 3.5 (used by spring-data since version 2.0.0 of spring-data-mongodb)
You can achieve to pass this option using MongoClientOptionsFactoryBean.
public MongoClientOptions mongoClientOptions() {
try {
final MongoClientOptionsFactoryBean bean = new MongoClientOptionsFactoryBean();
bean.setSocketKeepAlive(true);
bean.afterPropertiesSet();
return bean.getObject();
} catch (final Exception e) {
throw new BeanCreationException(e.getMessage(), e);
}
}
Here an example of this configuration by extending AbstractMongoConfiguration:
#Configuration
public class DataportalApplicationConfig extends AbstractMongoConfiguration {
//#Value: inject property values into components
#Value("${spring.data.mongodb.uri}")
private String uri;
#Value("${spring.data.mongodb.database}")
private String database;
/**
* Configure the MongoClient with the uri
*
* #return MongoClient.class
*/
#Override
public MongoClient mongoClient() {
return new MongoClient(new MongoClientURI(uri,mongoClientOptions().builder()));
}
My journey begins with me trying to configure Java driver of MongoDB to use UUID v4 instead of Legacy UUID v3 which is set by default.
I've found this solution here https://groups.google.com/forum/#!msg/mongodb-user/ZJKQpMpCMU4/dW5ATHTcAvgJ which works.
But as he states:
Note that when using the legacy API the codec registry is ignored, so
this will not use the overridden UUIDCodec
it doesn't work with my MongoRepositoy.
This is my actual configuration:
#Bean
public MongoDbFactory mongoDbFactory() throws Exception {
ServerAddress server = new ServerAddress(host,port);
MongoClientOptions.Builder mcoBuilder = MongoClientOptions.builder();
CodecRegistry codecRegistry = fromRegistries(fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
MongoClient.getDefaultCodecRegistry());
mcoBuilder.codecRegistry(codecRegistry).build();
MongoClientOptions options = mcoBuilder.build();
MongoClient mongoClient = new MongoClient(server,options);
return new SimpleMongoDbFactory(mongoClient, mongoDataBase);
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory());
return mongoTemplate;
}
If I do:
mongoClient.getDatabase(mongoDataBase).getCollection("test")
.insertOne(new Document("_id",UUID.randomUUID()));
I get:
{ "_id" : BinData(4,"f0u8ig4TS6KaJGK93xmvNw==") }
Otherwise:
mongoTemplate.getCollection("test")
.insert(new BasicDBObject("_id", UUID.randomUUID()));
result on:
{ "_id" : BinData(3,"mUX4PTPBJo6bIjPufHf0vg==") }
I know MongoRepository uses MongoTemplate, although I've set the instance to use MongoClient and not the old Mongo, still not working. Is there any solution?
MongoClient extends Mongo, which has reference to legacy api DB class via getDB(). Although you've registered the new UUID codec with MongoClient which can only be used when you use getDatabase() to get MongoDatabase which spring mongo template current version doesn't and uses getDB(). So your changes to registry are never used.
Spring MongoDB 2.0.0 versions has been updated to use new java driver api. So your changes should work as expected against 2.0.0 version.
http://docs.spring.io/spring-data/data-mongo/docs/2.0.0.M4/reference/html/
In the StackExchange.Redis docs it is recommended to only create one and reuse the connection to Redis.
Azure Redis best practices recommends using the following pattern:
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect("cachename.redis.cache.windows.net,ssl=true,abortConnect=false,password=password");
});
public static ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
but how should I get this working with Autofac where I want the configuration to be set in the web/app config files?
I currently have a RedisCacheProvider:
private readonly ConnectionMultiplexer _connection;
public RedisCacheProvider(string config)
{
_connection = ConnectionMultiplexer.Connect(config);
}
and in my Autofac config:
builder.RegisterType<RedisCacheProvider>().As<ICacheProvider>().WithParameter("config", "localhost");
My thinking is, I should change my RedisCacheProvider to take in a ConnectionMultiplexer which is passed in via the static variable?
Update: My Solution so far:
My RedisCacheProvider (injecting an interface here allows me to mock the connection in unit tests):
private readonly IConnectionMultiplexer _connection;
public RedisCacheProvider(IConnectionMultiplexer connection)
{
_connection = connection;
}
RedisConnection class to hold the static property and read config from config file:
public class RedisConnection
{
private static readonly Lazy<ConnectionMultiplexer> LazyConnection =
new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisCache"]));
public static ConnectionMultiplexer Connection
{
get
{
return LazyConnection.Value;
}
}
}
Registration in an Autofac Module:
builder.RegisterType<RedisCacheProvider>().As<ICacheProvider>()
.WithParameter(new TypedParameter(
typeof(IConnectionMultiplexer),
RedisConnection.Connection));
Autofac supports Implicit Relationship Types and Lazy<> evaluation is supported out of the box.
So after you register your RedisCacheProvider as in your example, that is
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>()
.WithParameter("config", "localhost");
you can resolve it like below:
container.Resolve<Lazy<ICacheProvider>>();
But do not forget that default Autofac lifetime scope is InstancePerDependency(transient). That is, you will get new instance of RedisCacheProvider everytime you resolve it or whenever it is provided to other component as dependency. To fix this you need to specify its lifetime scope explicitly. For instance, to make it singleton you need to change registration as below:
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>()
.WithParameter("config", "localhost")
.SingleInstance();
Another assumption here is that RedisCacheProvider is the only component where Redis connection is used. If it is not the case then you should better let Autofac manage Redis connection's life scope (which is a better idea anyway) and get the connection as a dependency in RedisCacheProvider. That is:
public RedisCacheProvider(IConnectionMultiplexer connection)
{
this.connection = connection;
}
....
builder
.Register(cx => ConnectionMultiplexer.Connect("localhost"))
.As<IConnectionMultiplexer>()
.SingleInstance();
builder
.RegisterType<RedisCacheProvider>()
.As<ICacheProvider>();
Do I have to have a domain object to query mongodb?
What if I just want some raw data to be displayed? What would be the syntax to query mongodb from my controller?
I tried
"def var = db.nameOfMyCollection.find()"
but it says no such property as db in my controller class.
I know that my application is connecting to the database because I am monitoring mongo server log and it increases the number of connections by one when I launch my grails app.
Assuming you have added mongodb java driver dependency in build config and refreshed your dependencies.
Create a grails service named MongoService.groovy and put the following code.
Dont forget to import mongodb
package com.organisation.project
import com.mongodb.*
class MongoService {
private static MongoClient mongoClient
private static host = "localhost" //your host name
private static port = 27017 //your port no.
private static databaseName = "your-mongo-db-name"
public static MongoClient client() {
if(mongoClient == null){
return new MongoClient(host,port)
}else {
return mongoClient
}
}
public DBCollection collection(collectionName) {
DB db = client().getDB(databaseName)
return db.getCollection(collectionName)
}
}
We can now use this MongoService in our controllers or other services.
Now you can do following stuff in your controller.
Dont forget to import mongodb.DBCursor
package com.organisation.project
import com.mongodb.DBCursor
class YourControllerOrService {
def mongoService //including Mongo service
def method(){
def collection = mongoService.collection("your-collection-name")
DBCursor cursor = collection.find()
try{
while(cursor.hasNext()){
def doc = cursor.next()
println doc //will print raw data if its in your database for that collection
}
}finally {
cursor.close()
}
}
}
For more info Refer mongodb java docs
Ok, solved.
This is how you go about accessing the database.
import com.mongodb.*
MongoClient mongoClient = new MongoClient("localhost", 27017)
DB db = mongoClient.getDB("db");
I actually solved it using Java and then pasted it into groovy and it works there as well which shouldn't come as a surprise. The difference is that in Java you actually have to import the jar driver, but in Grails, you install the Mongo GORM plugin.
Assuming you are using the MongoDB GORM Plugin, if you have domain classes in your grails application, you can use them as you would with any relational db backend.
However, per this documentation, you can access the low-level Mongo API in any controller or service by first declaring a property mongo, just as you would a service, then getting the database you are targeting:
def mongo
def myAction = {
def db = mongo.getDB("mongo")
db.languages.insert([name: 'Groovy'])
}