Giter VIP home page Giter VIP logo

aliyun_oss's People

Contributors

c4710n avatar cswen avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar ug0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

aliyun_oss's Issues

object_url can't support put object

def object_url(bucket, object, expires) do
signature =
%{
verb: "GET",
host: "#{bucket}.#{endpoint()}",
path: "",
resource: "/#{bucket}/#{object}",
query_params: %{},
sub_resources: %{},
headers: %{
"Date" => Integer.to_string(expires),
"Content-Type" => ""
}
}

As currently you hardcoded to GET, and for put object also need to pass the content type in the header

When you put object with signatureUrl ,you need to pass Content-Type

Can we add one more overload function to handle this case?

If you have any idea, please let me know

The way to use this library for different Aliyun OSS endpoints

Thanks

Hi, @ug0, thanks for implementing this great library.

The problem

Recently, I have to operate on different Aliyun OSS endpoints.
But,:aliyun_oss is using Application.get_env/2 to getting the global configurations, which makes it impossible to setup :aliyun_oss for different Aliyun OSS endpoints. For example, in config/runtime.exs:

# these configurations are global. No chance to opt out ;(
config :aliyun_oss,
  endpoint: "oss-cn-shenzhen.aliyuncs.com",
  access_key_id: System.fetch_env!("ALIYUN_ACCESS_KEY_ID"),
  access_key_secret: System.fetch_env!("ALIYUN_ACCESS_KEY_SECRET")

I want to solve this problem in a more community-friendly way - don't fork this repo or create my own implementation, which wastes people's energy and weakens community's cohesion.

Some official guidelines

I searched a lot about how to solve this problem. And I found, in the official docs, there is some guidelines about the design of a library:

The application environment should be reserved only for configurations that are truly global, for example, to control your application boot process and its supervision tree. And, generally speaking, it is best to avoid global configuration. If you must use configuration, then prefer runtime configuration instead of compile-time configuration. See the Application module for more information.

For all remaining scenarios, libraries should not force their users to use the application environment for configuration. If the user of a library believes that certain parameter should be configured globally, then they can wrap the library functionality with their own application environment configuration.

Don't misunderstand me, I'm not saying you are doing it wrong. It just doesn't meet the needs at the moment, so maybe we need to make some changes.

A possible solution

local configuration provided as a function argument

If we don't use global configuration any more, what should we use? Local configuration provided as a function argument.

Take Aliyun.Oss.Bucket.list_buckets/1 as an example, the current function signature is:

list_buckets(query_params \\ %{})

After adding a local config, it becomes:

list_buckets(config, query_params \\ %{})

improve the type of config

It's bad to use a random map as configuration, we can do it better - reuse existing Aliyun.Oss.Config and use it provide a %Config{} struct, such as:

defmodule Aliyun.Oss.Config do
  @enforce_keys [:endpoint, :access_key_id, :access_key_secret]
  defstruct @enforce_keys

  @type config() :: %{
          endpoint: String.t(),
          access_key_id: String.t(),
          access_key_secret: String.t()
        }

  @type t :: %__MODULE__{
          endpoint: String.t(),
          access_key_id: String.t(),
          access_key_secret: String.t()
        }

  @spec new!(config()) :: __MODULE__.t()
  def new!(config) when is_map(config) do
    config
    # we can do more checks here
    |> as_struct!()
  end

  defp as_struct!(config) do
    struct!(__MODULE__, config)
  end
end

And the function signature becomes:

alias Aliyun.Oss.Config
list_buckets(%Config{}, query_params \\ %{})

When we want to call this API, we do this:

alias Aliyun.Oss.Config

config = Config.new!(%{
  endpoint: "...",
  access_key_id: "...",
  access_key_secret: "..."
})

list_buckets(config, %{})

Is the possible solution works?

Suppose that I have 2 Aliyun OSS endpoints, and I want to operate on them with two different modules. Then I can implement something like this:

defmodule MyApp.OssA do
  alias Aliyun.Oss.Config
  alias Aliyun.Oss.Bucket

  def list_buckets(query_params \\ %{}) do
    Bucket.list_buckets(config(), query_params)
  end

  def config() do
    :my_app
    |> Application.fetch_env!(MyApp.OssA)
    |> Config.new!()
  end
end

# In the config/runtime.exs
config :my_app, MyApp.OssA,
  endpoint: "...",
  access_key_id: "...",
  access_key_secret: "..."
defmodule MyApp.OssB do
  alias Aliyun.Oss.Config
  alias Aliyun.Oss.Bucket

  def list_buckets(query_params \\ %{}) do
    Bucket.list_buckets(config(), query_params)
  end

  def config() do
    :my_app
    |> Application.fetch_env!(MyApp.OssB)
    |> Config.new!()
  end
end

# In the config/runtime.exs
config :my_app, MyApp.OssB,
  endpoint: "...",
  access_key_id: "...",
  access_key_secret: "..."

These two modules are using different configurations as expected.

"Wait. Does that means users have to map every API?"

Users don't have to every API, but only the API they need.

In real apps, it's rare that all the API provided by :aliyun_oss are required. In general, only a few API are required.

From the perspective of code organization, it's also recommended to map the functions provided by :aliyun_oss to a self-owned module, which helps to abstract the terminology of OSS from business logic, such as a module for managing images:

defmodule MyApp.ImageManagement do
  def upload_image(binary) when is_binary do
    Aliyun.Oss.put_object(
      #...
    )
  end
  
  # more actions, such as get, delete, etc.
end

"Oh, Good. But what should I do?"

The things that need to be done are:

  • adjusting all the public functions to accept 1st argument as config.
  • removing the calls of global configuration.

But I'm not going to let you do it, after all you've done all of this library.

If you like my ideas and trust me, I can do this.

Last

Any ideas? Please let me know.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.