Conexión a MongoDB en Golang -- performance campo con beginner campo con go campo con mongodb camp codereview Relacionados El problema

connection to mongodb in golang


1
vote

problema

Español

Estoy construyendo un servidor API en Golang con MongoDB. MI main.go El archivo se ve algo así:

  9988777665544337  

Cada controlador tiene alguna lógica que cambia los datos en MongoDB. Independientemente de lo que esté haciendo con la colección de preguntas, esto es común en todos los manipuladores.

  9988776655544338  

Estas funciones están en handler/handler.go .

Como puedes ver, estoy usando el conductor oficial de Mongo. ¿Cuál es el mejor tipo de refactor que se puede hacer?

Original en ingles

I am building an API server in golang with mongodb. My main.go file looks something like this:

func main() {     r := mux.NewRouter()        question := r.PathPrefix("/question").Subrouter()     question.HandleFunc("", handler.PostQuestion).Methods("POST")     question.HandleFunc("/{id}", handler.UpdateQuestion).Methods("PUT")     question.HandleFunc("/{id}", handler.DeleteQuestion).Methods("DELETE")     question.HandleFunc("/{id}", handler.GetQuestion).Methods("GET")     r.HandleFunc("/questions", handler.GetAllQuestions).Methods("GET")      if err := http.ListenAndServe(":8080", r); err != nil {         log.Fatalf("could not listen on port 8080 %v", err)     } } 

Every handler has some logic which changes data in MongoDB. Regardless of what I am doing with the question collection, this is common in all the handlers.

client, _ := mongo.NewClient(options.Client().ApplyURI("mongodb://127.0.0.1:27017")) ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) err := client.Connect(ctx) if err != nil {     panic(err) }  defer client.Disconnect(ctx) collection := client.Database("myapp").Collection("questions") 

These functions are in handler/handler.go.

As you can see, I am using the official Mongo driver. What is the best kind of refactor which can be done?

           
 
 

Lista de respuestas

3
 
vote
vote
La mejor respuesta
 

Usted dice que esto es común en todos los manejadores:

  client, _ := mongo.NewClient(options.Client().ApplyURI("mongodb://127.0.0.1:27017")) ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) err := client.Connect(ctx) if err != nil {     panic(err) }  defer client.Disconnect(ctx) collection := client.Database("myapp").Collection("questions")   

Por lo tanto, esto significa que está conectando y desconectando a MongoDB para cada solicitud . Eso es increíblemente inútil. El cliente MongoDB es seguro para su uso concurrente, por lo que puede reutilizar la conexión. Porque veo un código como este:

  question.HandleFunc("", handler.PostQuestion).Methods("POST")   

Sé que las funciones de su manejador son métodos, por lo que no agrega el cliente a su tipo de controlador:

  type yourHandler struct {     coll   *mongo.Collection }   

Luego, en sus controladores, puede simplemente acceder:

  func (h *yourHandler) PostQuestion(w http.ResponseWriter, r *http.Request) {     if err := h.coll.InsertOne(r.Context(), ...); err != nil {         // write error response         return     } }   

Ahora, su función principal se verá un poco diferente, ya que ya no estamos desconectando el cliente Mongo en el controlador, y no estamos usando un contexto con un tiempo de espera arbitrario. Sugiero algo como esto:

  func main() {     // create the base context, the context in which your application runs     ctx, cfunc := context.WithCancel(ctx.Background())     defer cfunc()     client, err := mongo.NewClient(         options.Client().ApplyURI("mongodb://127.0.0.1:27017"))     if err != nil {         log.Fatalf("Error creating mongo client: %+v", err)     }     defer client.Disconnect(ctx) // add disconnect call here     if err := client.Connect(ctx); err != nil {         log.Fatalf("Failed to connect to MongoDB: %+v", err)     }     // set collection on handler     handler.coll = client.Database("myapp").Collection("questions")     // rest of the code here }   

Así que ahora, siempre que la función principal vuelva, se cancela el contexto de la aplicación (que se usa para conectar el cliente) y se llama client.Disconnect5 . El controlador tiene acceso a la colección que está utilizando, eliminando un montón de código duplicado y eliminando la sobrecarga de conectar y desconectar constantemente a MongoDB.

Dentro de su manejador, no estoy usando context.WithTimeout(context.Background(), 10*time.Second) . En su lugar, estoy usando el contexto de solicitud, lo que significa una vez que se cancela el contexto de la solicitud, el contexto de la consulta es. Todavía podría establecer un límite de tiempo si lo desea:

  func (h *yourHandler) PostQuestion(w http.ResponseWriter, r *http.Request) {     // context expires if request context does, or times out in 3 seconds     ctx, cfunc := context.WithTimeout(r.Context(), time.Second * 3)     defer cfunc() // good form to add this     if err := h.coll.InsertOne(ctx, ...); err != nil {         // error response etc...         return     }     // success response }   

Actualmente nuestro manejador requiere el campo coll para ser del tipo *mongo.Collection , lo que dificulta la prueba de su código. En su lugar, es posible que desee cambiar ese campo para tomar una interfaz:

  question.HandleFunc("", handler.PostQuestion).Methods("POST") 0  

En las pruebas de su unidad, ahora puede inyectar una interfaz de recolección simulada, que puede controlar, lo que le permite simular devoluciones de errores, etc.

En lugar de exponer el campo question.HandleFunc("", handler.PostQuestion).Methods("POST") 1 en su controlador, entonces, también querrá crear una función de constructor para inyectar las dependencias de lo que tengo en la función principal arriba ( 99887776655443312 ):

  question.HandleFunc("", handler.PostQuestion).Methods("POST") 3  

Te dejaré con esto como un punto de partida. Solo una cosa que aún no he mencionado: está usando question.HandleFunc("", handler.PostQuestion).Methods("POST") 4 cuando 99887766555443315 devuelve un error. Esta bien. ¿Por qué estás usando question.HandleFunc("", handler.PostQuestion).Methods("POST") 6 cuando question.HandleFunc("", handler.PostQuestion).Methods("POST") 7 falla?

Pánico es algo que debe evitar tanto como sea posible. Especialmente aquí: agrega muy poco (a NO) valor: la única información que un volcado de pánico se producirá es el paradero en el paquete de Mongo, algo se fue mal. No controla ese paquete, por lo que solo se prefiere 99887766555443318 .


Más detalles

Ver a medida que las funciones de su manejador son realmente funciones en un paquete, en lugar de métodos (que inicialmente pensé que eran):

Refactor El paquete question.HandleFunc("", handler.PostQuestion).Methods("POST") 9 para contener un constructor y tener las funciones reales del controlador como métodos en un tipo. Algo así como:

  type yourHandler struct {     coll   *mongo.Collection } 0  

Desde su paquete type yourHandler struct { coll *mongo.Collection } 1 ", esto se verá así:

  type yourHandler struct {     coll   *mongo.Collection } 2  
 

You say this is common in all handlers:

client, _ := mongo.NewClient(options.Client().ApplyURI("mongodb://127.0.0.1:27017")) ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) err := client.Connect(ctx) if err != nil {     panic(err) }  defer client.Disconnect(ctx) collection := client.Database("myapp").Collection("questions") 

So this means you're connecting and disconnecting to MongoDB for each request. That's incredibly wasteful. The MongoDB client is safe for concurrent use, so you can reuse the connection. Because I see code like this:

question.HandleFunc("", handler.PostQuestion).Methods("POST") 

I know your handler functions are methods, so why not add the client to your handler type:

type yourHandler struct {     coll   *mongo.Collection } 

Then, in your handlers you can simply access:

func (h *yourHandler) PostQuestion(w http.ResponseWriter, r *http.Request) {     if err := h.coll.InsertOne(r.Context(), ...); err != nil {         // write error response         return     } } 

Now your main function will look a bit different, seeing as we're not disconnecting the mongo client in the handler anymore, and we're not using a context with an arbitrary timeout. I suggest something like this:

func main() {     // create the base context, the context in which your application runs     ctx, cfunc := context.WithCancel(ctx.Background())     defer cfunc()     client, err := mongo.NewClient(         options.Client().ApplyURI("mongodb://127.0.0.1:27017"))     if err != nil {         log.Fatalf("Error creating mongo client: %+v", err)     }     defer client.Disconnect(ctx) // add disconnect call here     if err := client.Connect(ctx); err != nil {         log.Fatalf("Failed to connect to MongoDB: %+v", err)     }     // set collection on handler     handler.coll = client.Database("myapp").Collection("questions")     // rest of the code here } 

So now, whenever the main function returns, the application context is cancelled (which is used to connect the client), and client.Disconnect is called. The handler has access to the collection you're using, removing a lot of duplicate code, and removing the overhead of constantly connecting and disconnecting to MongoDB.

Inside your handler, I'm not using context.WithTimeout(context.Background(), 10*time.Second). Instead I'm using the request context, which means once the request context is cancelled, the query context is. You could still set a time-limit if you want:

func (h *yourHandler) PostQuestion(w http.ResponseWriter, r *http.Request) {     // context expires if request context does, or times out in 3 seconds     ctx, cfunc := context.WithTimeout(r.Context(), time.Second * 3)     defer cfunc() // good form to add this     if err := h.coll.InsertOne(ctx, ...); err != nil {         // error response etc...         return     }     // success response } 

Currently our handler requires the field coll to be of the type *mongo.Collection, which makes it harder to test your code. Instead, you might want to change that field to take an interface:

//go:generate go run github.com/golang/mock/mockgen -destination mocks/collection_interface_mock.go -package mocks your.module.io/path/to/handler/package Collection type Collection interface{     InsertOne(ctx context.Context, document interface{}, opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error)     Name() string     // all methods you need } 

In your unit tests, you can now inject a mock collection interface, that you can control, allowing you to simulate error returns, etc...

Instead of exposing the coll field on your handler, then, you'll also want to create a constructor function to inject the dependencies of what I have in the main function above (handler.coll = ...):

package service  type Collection interface{} // interface as above, with go generate comment  type handler struct {     coll Collection }  func NewHandler(c Collection) *handler {     return &handler{         coll: c,     } } 

I'll leave you with this as a starting point. Just one thing I've not yet mentioned: you're using log.Fatalf when http.ListenAndServe returns an error. That's fine. Why are you using panic(err) when client.Connect(ctx) fails?

Panic is something you should avoid as much as possible. Especially here: it adds very little (to no) value: the only information a panic dump will yield is whereabouts in the mongo package something went awry. You don't control that package, so just log.Fatalf("Mongo error: %+v", err) is to be preferred.


More details

Seeing as your handler functions are actually functions in a package, rather than methods (which I initially thought they were):

I'd refactor the handler package to contain a constructor, and have the actual handler functions as methods on a type. Something like:

package handler  type Collection interface{     InsertOne(ctx context.Context, document interface{}, opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error)     Name() string     // and so on }  // main handler type type Handler struct {     coll Collection }  // New returns the handler func New(c Collection) *Handler {     return &Handler{         coll: c,     } }  func (h *Handler) PostQuestion(w http.ResponseWriter, r *http.Request) {     if err := h.coll.InsertOne(r.Context(), ...); err != nil {         log.Errorf("Insert question error: %+v", err)         // write response         return     }     // success response } 

From your main package, this will look like this:

func main() {     ctx, cfunc := context.WithCancel(ctx.Background())     defer cfunc()     client, err := mongo.NewClient(         options.Client().ApplyURI("mongodb://127.0.0.1:27017"))     if err != nil {         log.Fatalf("Error creating mongo client: %+v", err)     }     defer client.Disconnect(ctx) // add disconnect call here     if err := client.Connect(ctx); err != nil {         log.Fatalf("Failed to connect to MongoDB: %+v", err)     }     // create handler with collection injected     myHandler := handler.New(client.Database("myapp").Collection("questions"))      // configure router to call methods on myHandler as handler functions     // the handlers will now have access to the collection we've set up here     r := mux.NewRouter()        question := r.PathPrefix("/question").Subrouter()     question.HandleFunc("", myHandler.PostQuestion).Methods("POST")     // and so on } 
 
 
         
         

Relacionados problema

2  Usando tipos para diseñar dominio  ( Using types to designing domain ) 
Estoy tratando de modelar un dominio de métricas para su uso en una solicitud de BI. Hace poco leí el Diseño con tipos serie, y sintió que podría ser útil ,...

7  Tirando de la máquina Docker IP  ( Pulling docker machine ip ) 
Estoy trabajando con MongoDB a través de Docker, y tengo un mando de Bash terrible para tirar de la máquina Docker Machine IP para que pueda conectarla a nive...

2  Uso síncrono del controlador Mongo-Scala  ( Synchronous use of mongo scala driver ) 
The Driver Mongo-Scala (v2.6) actualmente solo admite operaciones asíncronas, aunque mis casos de uso a menudo parecen prestarse bien a las lecturas síncron...

4  Optimización de las uniones de ASYNC para MongoDB (mangosta) usando async.js  ( Optimizing async joins for mongodb mongoose using async js ) 
Estoy construyendo una aplicación web de Media Viewer para un quiosco en nodo que usa datos en Mongo. Para representar al espectador, reúne todos los objetos ...

2  Cree una solicitud de transmisión para el historial de transacciones por ID de cliente en Braintree Vault  ( Create a stream request for transaction history by client id on braintree vault ) 
He estado tratando de crear una solicitud de historial de transacciones de Braintree utilizando un ID de cliente en Braintree Vault, y se tardó mucho en descu...

6  Libro de direcciones de teléfono simple  ( Simple telephone address book ) 
Soy nuevo en la programación de Java, y para ayudarme a aprender, creé una simple libreta de direcciones telefónicas. El código se ejecuta como se esperaba, p...

3  Bash Script para implementar un clúster de MongoDB en la máquina local  ( Bash script to deploy a mongodb cluster on local machine ) 
Recientemente escribí un pequeño script para implementar una mongodb cluster en un Máquina única. El clúster está compuesto por: 3 Configurations Serve...

1  Administración de módulos en la aplicación de la guía telefónica  ( Managing modules in phone book app ) 
Esta es una aplicación de prueba construida con NODEJS, Express y Mongodb. Este código es el archivo JavaScript principal y completo. Me gustaría sugerencias ...

4  Conexión mongodb de un solo nodo JS  ( Node js single mongodb connection ) 
Quiero que toda mi aplicación de nodo. Utilice una sola conexión MongoDB, al menos eso es lo que creo que es mejor. Escribí este pequeño script y quiero un co...

4  Implementación de MongoDB similar al usuario  ( User like mongodb implementation ) 
Esta es mi primera implementación de Mongodb, mucho más por venir. Esto arroja luz sobre algunas cosas como cómo he estructurado mi colección. Espero obtener ...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos