Home Blog Page 195

Modern C++ binary RPC framework gọn nhẹ, không cần code generation

Bài viết sẽ bàn về một framework C++ RP, ko yêu cầu bước code generation để glue code. Trước khi đi vào chi tiết, hãy tìm hiểu một số tính năng cơ bản của công cụ:

  • Mã nguồn tại https://bitbucket.org/ruifig/czrpc
    • Đoạn mã nguồn trên vẫn chưa hoàn chỉnh. Mục tiêu của nó chỉ là để thể hiện nền tảng xây dựng framework. Hơn nữa, để rút ngắn, đoạn code là hỗn hợp của code từ repo lúc viết và custom sample code, nên có thể sẽ xảy ra lỗi.
    • Một số đoạn trong mã nguồn (không liên quan trực tiếp đến nội dung ta đang bàn đến) sẽ chỉ được xem xét nhanh mà không quá quan tâm đến hiệu năng. Bất cứ cải tiến nào sẽ được thêm vào repo mã nguồn sau.
  • Modern C++ (C++11/14)
    • Yêu cầu ít nhất Visual Studio 2015. Clang/GCC cũng tạm ổn.
  • Type-safe
    • Framework xác định những RPC call không hợp lệ trong lúc compile, như tên RPC không rõ, số thông số sai, hoặc kiểu thông số sai.
  • API tương đối nhỏ, không dài dòng
  • Nhiều cách xử lý RPC reply
    • Bộ xử lý không đồng bộ (Asynchronous handler)
    • Futures
    • Client có thể xác định nếu một RPC gây exception bên phía server
  • Cho phép sử dụng (gần như) bất kỳ kiểu RPC parameter nào
    • Giả sử nguời dùng chạy đúng hàm để xử lý kiểu parameter đó
  • RPC hai chiều (server có thể call RPC trên client)
    • Thông thường, client code không thể tin tưởng được, nhưng vì framework nhằm sử dụng giữa các bên uy tín, nên đây không phải là vấn đề quá lớn
  • Không xâm nhập
    • Một object dùng cho RPC call không cần biết bất cứ thứ gì về RPC hoặc network cả.
    • Từ đó có thể bọc các class bên thứ ba cho RPC call.
  • Chi phí băng thông tối thiểu mỗi RPC call
  • Không phụ thuộc bên ngoài
    • Dù supplied transport (trong repo mã nguồn) dùng Asio/Boost Asio, nhưng bản thân framework không phụ thuộc vòa đó. Bạn có thể tự cắm transport của mình.
  • Không có tính năng bảo mật
    • Vì frame work được thiết kế sử dụng cho các bên tin tưởng lẫn nhau (như giữa server chẳng hạn).
    • Ứng dụng có thể tự chọn transport riêng, nên sẽ có cơ hội encrypt bất cứ thứ gì nếu cần thiết.

Có thể bạn muốn xem:

  Clean Code là cái của nợ gì?
  Tìm hiểu về nguyên lý "vàng" SOLID trong lập trình hướng đối tượng

Mặc dù mã nguồn vẫn chưa hoàn thiện, bài viết vẫn rất nặng về code. Code được tách thành nhiều phần nhỏ và một phần sau sẽ dựa theo phần trước. Để bạn hình dung được, sau đây là một mẫu code hoạt động hoàn chỉnh sử dụng repo mã nguồn tại thời điểm viết bài:

//////////////////////////////////////////////////////////////////////////
// Useless RPC-agnostic class that performs calculations.
//////////////////////////////////////////////////////////////////////////
class Calculator {
public:
    double add(double a, double b) { return a + b; }
};

//////////////////////////////////////////////////////////////////////////
// Define the RPC table for the Calculator class
// This needs to be seen by both the server and client code
//////////////////////////////////////////////////////////////////////////
#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    REGISTERRPC(add)
#include "crazygaze/rpc/RPCGenerate.h"

//////////////////////////////////////////////////////////////////////////
// A Server that only accepts 1 client, then shuts down
// when the client disconnects
//////////////////////////////////////////////////////////////////////////
void RunServer() {
    asio::io_service io;
    // Start thread to run Asio's the io_service
    // we will be using for the server
    std::thread th = std::thread([&io] {
        asio::io_service::work w(io);
        io.run();
    });

    // Instance we will be using to serve RPC calls.
    // Note that it's an object that knows nothing about RPCs
    Calculator calc;

    // start listening for a client connection.
    // We specify what Calculator instance clients will use,
    auto acceptor = AsioTransportAcceptor<Calculator, void>::create(io, calc);
    // Start listening on port 9000.
    // For simplicity, we are only expecting 1 client
    using ConType = Connection<Calculator, void>;
    std::shared_ptr<ConType> con;
    acceptor->start(9000, [&io, &con](std::shared_ptr<ConType> con_) {
        con = con_;
        // Since this is just a sample, close the server once the first client
        // disconnects
        reinterpret_cast<BaseAsioTransport*>(con->transport.get())
            ->setOnClosed([&io] { io.stop(); });
    });

    th.join();
}

//////////////////////////////////////////////////////////////////////////
// A client that connects to the server, calls 1 RPC
// then disconnects, causing everything to shut down
//////////////////////////////////////////////////////////////////////////
void RunClient() {
    // Start a thread to run our Asio io_service
    asio::io_service io;
    std::thread th = std::thread([&io] {
        asio::io_service::work w(io);
        io.run();
    });

    // Connect to the server (localhost, port 9000)
    auto con =
        AsioTransport<void, Calculator>::create(io, "127.0.0.1", 9000).get();

    // Call one RPC (the add method), specifying an asynchronous handler for
    // when the result arrives
    CZRPC_CALL(*con, add, 1, 2)
        .async([&io](Result<double> res) {
            printf("Result=%f\n", res.get());  // Prints 3.0
            // Since this is a just a sample, stop the io_service after we get
            // the result,
            // so everything shuts down
            io.stop();
        });

    th.join();
}

// For testing simplicity, run both the server and client on the same machine,
void RunServerAndClient() {
    auto a = std::thread([] { RunServer(); });
    auto b = std::thread([] { RunClient(); });
    a.join();
    b.join();
}

Đây đa phần là setup code, vì transport được nhắc đến sử dụng Asio. Bản thân RPC có thể đơn giản như:

// RPC call using asynchronous handler to handle the result
CZRPC_CALL(*con, add, 1, 2).async([](Result<double> res) {
    printf("Result=%f\n", res.get());  // Prints 3.0
});

// RPC call using std::future to handle the result
Result<double> res = CZRPC_CALL(*con, add, 1, 2).ft().get();
printf("Result=%f\n", res.get());  // Prints 3.0

Tìm việc làm C++ nhanh chóng trên TopDev

Lý do tôi viết công cụ này

Tôi đang làm việc với một game có code named G4 cũng được vài năm rồi, trò chơi cung cấp cho player những chiếc máy tính mô phỏng nhỏ in-game mà thậm chí có thể code được. Điều này yêu cầu tôi phải cho nhiều server cùng chạy song song:

  • Gameplay Server(s)
  • VM Server(s) (mô phỏng máy tính ingame)
    • Để vẫn có thể mô phỏng máy tính ngay cả khi người chơi hiện không online
  • VM Disk Server(s)
    • Xử lý lưu trữ của máy tính ingame, như đĩa mềm hoặc ổ cứng.
  • Database server(s)
  • Login server(s)

Tất cả những server này cần trao đổi dữ liệu, vì vậy ta cần một RPC framework linh hoạt.

Ban đầu, giải pháp tôi tìm đến là tag method của một class với attribute cụ thể, rồi từ parser gốc Clang (clReflect) tạo bất cứ serialization cần thiết nào, và glue code.

Dù cách này vẫn áp dụng được, nhưng nhiều năm qua tôi vẫn luôn canh cánh làm cách nào tôi có thể dùng tính năng C++11/14 mới để tạo một minimal type safe C++ RPC framework, một phương pháp không cần bước code generation cho glue code, mà vẫn giữa một API chấp nhận được.

Với serialization của non-fundamental type, code genaration vẫn còn hữu dụng, vậy nên tôi không cần phải xác định thủ công làm sao để serialize tất cả các field của một struct/class cho trước. (dù khâu xác định thủ công này cũng chả phiền phức cho lắm).

RPC Parameters

Xét một function, để có type safe RPC call, ta phải làm được:

  • Xác định lúc compile nếu function là một RPC function hợp lệ (đúng số thông ố, đúng kiểu parameter,…)
  • Kiểm tra liệu các parameter được supply khớp (hoặc convert được) sang thứ mà function signature chỉ ra.
  • Serialize tất cả parameter.
  • Deserialize tất cả parameter.
  • Call những function mong muốn

Parameter Traits

Vấn đề đầu tiên bạn đối mặt là việc xác định kiểu parameter nào được chấp nhập. Một số RPC framework chỉ chấp nhật một số kiểu giới hạn, như Thrift. Hãy xem thử vấn đề.

Xét các function signature sau:

void func1(int a, float b, char c);
void func2(const char* a, std::string b, const std::string& c);
void func3(Foo a, Bar* b);

Vậy làm cách nào ta bắt compile kiểm tra theo parameter? Những kiểu fundamental type khá dễ và chắc chắn phải được framework hỗ trợ. Một bản copy dumb memory là đủ cho trường hợp như thế này, trừ khi bạn muốn cắt giảm số bit cần thiết để hy sinh một ít hiệu năng lấy bandwidth. Vậy chòn với các kiểu phức tạp như std::string, std::vector, hoặc chính class của mình? Còn pointer, reference, const reference, rvalue thì sao?

Ta có thể lấy một số ý tưởng từ những thứ C++ Standard Library đang làm trong type_traits header. Ta cần truy vấn một kiểu cho trước dựa theo tính chất RPC của nó. Hãy thử biến ý tưởng trên thành một template class `ParamTraits<T>`, với layout như sau:

Member constants
valid true if T is valid for RPC parameters, false otherwise
Member types
store_type Type dùng để giữ copy tạm thời cần cho deserializing
Member functions
write Ghi parameter đến một stream
read Đọc parameter vào một store_type
get Xét một store_type parameter, kết quả nó trả có thể được chuyển đến RPC function làm parameter

Trong ví dụ này,hãy thực thi `ParamTraits<T>` cho kiểu thuật toán, trong trường hợp ta có stream class với method `write` và `read`:

namespace cz {
namespace rpc {
// By default, all types for which ParamTraits is not specialized are invalid
template <typename T, typename ENABLED = void>
struct ParamTraits {
    using store_type = int;
    static constexpr bool valid = false;
};

// Specialization for arithmetic types
template <typename T>
struct ParamTraits<
    T, typename std::enable_if<std::is_arithmetic<T>::value>::type> {

    using store_type = typename std::decay<T>::type;
    static constexpr bool valid = true;

    template <typename S>
    static void write(S& s, typename std::decay<T>::type v) {
        s.write(&v, sizeof(v));
    }

    template <typename S>
    static void read(S& s, store_type& v) {
        s.read(&v, sizeof(v));
    }

    static store_type get(store_type v) {
        return v;
    }
};

}  // namespace rpc
}  // namespace cz

Và một ví dụ đơn giản:

#define TEST(exp) printf("%s = %s\n", #exp, exp ? "true" : "false");

void testArithmetic() {
    TEST(ParamTraits<int>::valid);         // true
    TEST(ParamTraits<const int>::valid);   // true
    TEST(ParamTraits<int&>::valid);        // false
    TEST(ParamTraits<const int&>::valid);  // false
}

`ParamTraits<T>` còn được sử dụng để kiểm tra nếu return type hợp lệ, và vì một void function hợp lệ, ta cần phải chuyên hóa `ParamTraits` cho void nữa.

namespace cz {
namespace rpc {

// void type is valid
template <>
struct ParamTraits<void> {
    static constexpr bool valid = true;
    using store_type = void;
};

}  // namespace rpc
}  // namespace cz

Một điều khá kỳ lạ là khi chuyên hóa cho `void` là còn chỉ định một `store_type` nữa. Chúng ta không thể sử dụng nó để lưu trữ bất cư thứ gì, nhưng sẽ làm một vìa đoạn template code về sau dễ dàng hơn.

Với những ví dụ `ParamTraits`, reference không phải là RPC parameter hợp lệ. Trong thực tế, bạn ít nhất sẽ muốn cho phép const reference, đặc biệt với fundamental type. Bạn có thể thêm một tweak để kích hoạt hỗ trợ cho `const T&` cho bất kỳ T hợp lệ nào khi ứng dụng của bạn cần đến.

// Make "const T&" valid for any valid T
#define CZRPC_ALLOW_CONST_LVALUE_REFS                                          \
    namespace cz {                                                             \
    namespace rpc {                                                            \
    template <typename T>                                                      \
    struct ParamTraits<const T&> : ParamTraits<T> {                            \
        static_assert(ParamTraits<T>::valid,                                   \
                      "Invalid RPC parameter type. Specialize ParamTraits if " \
                      "required.");                                            \
    };                                                                         \
    }                                                                          \
    }

Bạn cũng có thể thực hiện những tweak tương tự để kích hoạt hỗ trợ `T&` hoặc `T&&` nếu cần thiết, mặc dù nếu function thay đổi đến những parameter này, những thay đổi đó sẽ mất đi.

Hãy thử thêm hỗ trợ cho các kiểu phức tạp như `std::vector<T>`. Để hỗ trợ `std::vector<T>`, ta buộc phải hỗ trợ thêm cả `T`:

namespace cz {
namespace rpc {

template <typename T>
struct ParamTraits<std::vector<T>> {
    using store_type = std::vector<T>;
    static constexpr bool valid = ParamTraits<T>::valid;
    static_assert(ParamTraits<T>::valid == true,
                  "T is not valid RPC parameter type.");

    // std::vector serialization is done by writing the vector size, followed by
    // each element
    template <typename S>
    static void write(S& s, const std::vector<T>& v) {
        int len = static_cast<int>(v.size());
        s.write(&len, sizeof(len));
        for (auto&& i : v) ParamTraits<T>::write(s, i);
    }

    template <typename S>
    static void read(S& s, std::vector<T>& v) {
        int len;
        s.read(&len, sizeof(len));
        v.clear();
        while (len--) {
            T i;
            ParamTraits<T>::read(s, i);
            v.push_back(std::move(i));
        }
    }

    static std::vector<T>&& get(std::vector<T>&& v) {
        return std::move(v);
    }
};

}  // namespace rpc
}  // namespace cz

// A simple test
void testVector() {
    TEST(ParamTraits<std::vector<int>>::valid);  // true
    // true if support for const refs was enabled
    TEST(ParamTraits<const std::vector<int>&>::valid);
}

Để tiện hơn, chúng ta có thể dùng toán tử `<<` và `>>`  với class `stream` (không thế hiện ở đây). Với những toán tử này, hãy đơn thuần call các function `ParamTraits<T> read` và `ParamTraits<T> write` tương ứng.

Giờ đây ta đã có thể kiểm tra xem một type nhất định có được phép cho RPC parameter hay không, chúng ta có thể build trên đó kiểm tra xem một function có được dùng cho RPC hay không. Cách này được thực thi với variadic template.

Trước hết hãy tạo một template cho ta biết nếu nhóm parameter có hợp lệ hay không.

namespace cz {
namespace rpc {

//
// Validate if all parameter types in a parameter pack can be used for RPC
// calls
//
template <typename... T>
struct ParamPack {
    static constexpr bool valid = true;
};

template <typename First>
struct ParamPack<First> {
    static constexpr bool valid = ParamTraits<First>::valid;
};

template <typename First, typename... Rest>
struct ParamPack<First, Rest...> {
    static constexpr bool valid =
        ParamTraits<First>::valid && ParamPack<Rest...>::valid;
}

}  // namespace rpc
}  // namespace cz

// Usage example:
void testParamPack() {
    TEST(ParamPack<>::valid);  // true (No parameters is a valid too)
    TEST((ParamPack<int, double>::valid));  // true
    TEST((ParamPack<int, int*>::valid));    // false
}
Using ParamPack, we can now create a FunctionTraits template to query a function's properties.
namespace cz {
namespace rpc {

template <class F>
struct FunctionTraits {};

// For free function pointers
template <class R, class... Args>
struct FunctionTraits<R (*)(Args...)> : public FunctionTraits<R(Args...)> {};

// For method pointers
template <class R, class C, class... Args>
struct FunctionTraits<R (C::*)(Args...)> : public FunctionTraits<R(Args...)> {
    using class_type = C;
};

// For const method pointers
template <class R, class C, class... Args>
struct FunctionTraits<R (C::*)(Args...) const>
    : public FunctionTraits<R(Args...)> {
    using class_type = C;
};

template <class R, class... Args>
struct FunctionTraits<R(Args...)> {
    // Tells if both the return type and parameters are valid for RPC calls
    static constexpr bool valid =
        ParamTraits<R>::valid && ParamPack<Args...>::valid;
    using return_type = R;
    // Number of parameters
    static constexpr std::size_t arity = sizeof...(Args);

    // A tuple that can store all parameters
    using param_tuple = std::tuple<typename ParamTraits<Args>::store_type...>;

    // Allows us to get the type of each parameter, given an index
    template <std::size_t N>
    struct argument {
        static_assert(N < arity, "error: invalid parameter index.");
        using type = typename std::tuple_element<N, std::tuple<Args...>>::type;
    };
};

}  // namespace rpc
}  // namespace cz

// A simple test...
struct FuncTraitsTest {
    void func1() const {}
    void func2(int) {}
    int func3(const std::vector<int>&) { return 0; }
    int* func4() { return 0; }
};

void testFunctionTraits() {
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func1)>::valid);  // true
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func2)>::valid);  // true
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func3)>::valid);  // true
    TEST(FunctionTraits<decltype(&FuncTraitsTest::func4)>::valid);  // false
}

`FunctionTraits` cho ta một vài tính chất sẽ được sử dụng sau. Lưu ý với ví dụ rằng `FunctionTraits::param_tuple` build trên `ParamTraits<T>::store_type`. Việc này cần thiết, vì ở một thời điểm nào đó, chung ta cần cách ể deserialize tất cả parameter thành một tuple trước khi call function.

Serialization

Vì chúng ta giờ đây có code cần thiết để truy vấn parameter, return type và validating function, chúng ta có thể gộp code lại để serialize một function call. Hơn nữa, đây chính là type safe. Nó sẽ không complie nếu nhập sai số hoặc kiểu parameter, hoặc nếu bản thân function không hợp lệ cho RPC (không hỗ trợ các return/parameter type).

namespace cz {
namespace rpc {

namespace details {

template <typename F, int N>
struct Parameters {
    template <typename S>
    static void serialize(S&) {}
    template <typename S, typename First, typename... Rest>
    static void serialize(S& s, First&& first, Rest&&... rest) {
        using Traits =
            ParamTraits<typename FunctionTraits<F>::template argument<N>::type>;
        Traits::write(s, std::forward<First>(first));
        Parameters<F, N + 1>::serialize(s, std::forward<Rest>(rest)...);
    }
};

}  // namespace details

template <typename F, typename... Args>
void serializeMethod(Stream& s, Args&&... args) {
    using Traits = FunctionTraits<F>;
    static_assert(Traits::valid,
                  "Function signature not valid for RPC calls. Check if "
                  "parameter types are valid");
    static_assert(Traits::arity == sizeof...(Args),
                  "Invalid number of parameters for RPC call.");
    details::Parameters<F, 0>::serialize(s, std::forward<Args>(args)...);
}

}  // namespace rpc
}  // namespace cz

//
// A simple test
// 
struct SerializeTest {
    void func1(int, const std::vector<int>) {}
    void func2(int*) {}
};

void testSerializeCall() {
    Stream s;
    serializeMethod<decltype(&SerializeTest::func1)>(s, 1,
                                                     std::vector<int>{1, 2, 3});

    // These fail to compile because of the wrong number of parameters
    // serializeMethod<decltype(&SerializeTest::func1)>(s);
    // serializeMethod<decltype(&SerializeTest::func1)>(s, 1);

    // Doesn't compile because of wrong type of parameters
    // serializeMethod<decltype(&SerializeTest::func1)>(s, 1, 2);

    // Doesn't compile because the function can't be used for RPCs.
    // int a;
    // serializeMethod<decltype(&SerializeTest::func2)>(s, &a);
}

Deserialization

Như đã đề cập ở trên, `FunctionTraits<F>::param_tuple` là type `std::tuple` ta dùng được để giữ tất cả parameter của function. Để có thể sử dụng tuple này để deserialize parameter, chúng ta phải chuyên hóa `ParamTraits` cho các tuple. Bên cạnh đó, ta còn có thể sử dụng `std::tuple` cho RPC parameter.

namespace cz {
namespace rpc {

namespace details {
template <typename T, bool Done, int N>
struct Tuple {
    template <typename S>
    static void deserialize(S& s, T& v) {
        s >> std::get<N>(v);
        Tuple<T, N == std::tuple_size<T>::value - 1, N + 1>::deserialize(s, v);
    }

    template <typename S>
    static void serialize(S& s, const T& v) {
        s << std::get<N>(v);
        Tuple<T, N == std::tuple_size<T>::value - 1, N + 1>::serialize(s, v);
    }
};

template <typename T, int N>
struct Tuple<T, true, N> {
    template <typename S>
    static void deserialize(S&, T&) {}
    template <typename S>
    static void serialize(S&, const T&) {}
};
}  // namespace details

template <typename... T>
struct ParamTraits<std::tuple<T...>> {
    using tuple_type = std::tuple<T...>;  // for internal use

    using store_type = tuple_type;
    static constexpr bool valid = ParamPack<T...>::valid;

    static_assert(
        ParamPack<T...>::valid == true,
        "One or more tuple elements is not a valid RPC parameter type.");

    template <typename S>
    static void write(S& s, const tuple_type& v) {
        details::Tuple<tuple_type, std::tuple_size<tuple_type>::value == 0,
                       0>::serialize(s, v);
    }

    template <typename S>
    static void read(S& s, tuple_type& v) {
        details::Tuple<tuple_type, std::tuple_size<tuple_type>::value == 0,
                       0>::deserialize(s, v);
    }

    static tuple_type&& get(tuple_type&& v) {
        return std::move(v);
    }
};

}  // namespace rpc
}  // namespace cz

// A simple test
void testDeserialization() {
    Stream s;
    serializeMethod<decltype(&SerializeTest::func1)>(s, 1,
                                                     std::vector<int>{1, 2});
    // deserialize the parameters into a tuple.
    // the tuple is of type std::tuple<int,std::vector<int>>
    FunctionTraits<decltype(&SerializeTest::func1)>::param_tuple params;
    s >> params;
};

Từ tuple đến function parameters

Sau khi deserialize tất cả parameter thành tuple, chúng ta giờ đâu phải tìm cách tháo tuble để call matching function. Một lần nữa, ta có thể dùng variadic template.

namespace cz {
namespace rpc {

namespace detail {
template <typename F, typename Tuple, bool Done, int Total, int... N>
struct callmethod_impl {
    static decltype(auto) call(typename FunctionTraits<F>::class_type& obj, F f,
                               Tuple&& t) {
        return callmethod_impl<F, Tuple, Total == 1 + sizeof...(N), Total, N...,
                               sizeof...(N)>::call(obj, f,
                                                   std::forward<Tuple>(t));
    }
};

template <typename F, typename Tuple, int Total, int... N>
struct callmethod_impl<F, Tuple, true, Total, N...> {
    static decltype(auto) call(typename FunctionTraits<F>::class_type& obj, F f,
                               Tuple&& t) {
        using Traits = FunctionTraits<F>;
        return (obj.*f)(
            ParamTraits<typename Traits::template argument<N>::type>::get(
                std::get<N>(std::forward<Tuple>(t)))...);
    }
};
}  // namespace details

template <typename F, typename Tuple>
decltype(auto) callMethod(typename FunctionTraits<F>::class_type& obj, F f,
                          Tuple&& t) {
    static_assert(FunctionTraits<F>::valid, "Function not usable as RPC");
    typedef typename std::decay<Tuple>::type ttype;
    return detail::callmethod_impl<
        F, Tuple, 0 == std::tuple_size<ttype>::value,
        std::tuple_size<ttype>::value>::call(obj, f, std::forward<Tuple>(t));
}

}  // namespace rpc
}  // namespace cz

// A simple test
void testCall() {
    Stream s;
    // serialize
    serializeMethod<decltype(&SerializeTest::func1)>(s, 1,
                                                     std::vector<int>{1, 2});
    // deserialize
    FunctionTraits<decltype(&SerializeTest::func1)>::param_tuple params;
    s >> params;
    // Call func1 on an object, unpacking the tuple into parameters
    SerializeTest obj;
    callMethod(obj, &SerializeTest::func1, std::move(params));
};

Vậy, giờ đây ta đã biết cách xác thực một function, serialize, deserialize, và call function đó. Vậy là đã xong bước code “level thấp” rồi. Lớp tiếp theo chúng ta sắp sửa phủ lên code này sẽ là RPC API thực sự.

The RPC API

Header

Header có chứa những thông tin sau:

Field
size Tổng dung lượng (byte) của RPC. Có dung lượng là một phần của header sẽ đơn giản hóa mạnh mẽ, vì chúng ta có thể kiểm tra nếu ta nhận tất cả dữ liệu trước khi thử xử lý RPC.
counter Số call. Mỗi lúc ta gọi RPC, một counter sẽ tăng và được chỉ định đến RPC call đó.
rpcid The function to call
isReply Nếu true, đây là reply đến RPC. Nếu false, đây là RPC call.
success Chỉ áp dụng cho reply (isReply==true). Nếu true, call thành công và data là reply. Nấu false, data là exception information

Counter và rpcid hình thành một key xác định RPC call instance, cần khi ghép một incoming RPC reply đế RPC call gây ra nó.

// Small utility struct to make it easier to work with the RPC headers
struct Header {
enum {
kSizeBits = 32,
kRPCIdBits = 8,
kCounterBits = 22,
};
explicit Header() {
static_assert(sizeof(*this) == sizeof(uint64_t),
“Invalid size. Check the bitfields”);
all_ = 0;
}

struct Bits {
uint32_t size : kSizeBits;
unsigned counter : kCounterBits;
unsigned rpcid : kRPCIdBits;
unsigned isReply : 1;  // Is it a reply to a RPC call ?
unsigned success : 1;  // Was the RPC call a success ?
};

uint32_t key() const {
return (bits.counter << kRPCIdBits) | bits.rpcid;
}
union {
Bits bits;
uint64_t all_;
};
};

inline Stream& operator<<(Stream& s, const Header& v) {
s << v.all_;
return s;
}

inline Stream& operator>>(Stream& s, Header& v) {
s >> v.all_;
return s;
}

}  // namespace rpc
}  // namespace cz

Table

Chúng ta đã serialize và deserialize một RPC, nhưng không phải là cách map một RPC đã serialize đến đúng function bên phía server. Để giải quyết vấn đề này, chúng ta cần chỉ định một ID đến mỗi function. Client sẽ biết nó muốn call function nào, và điền đúng ID vào header. Server kiểm tra header, và khi biết được ID, nó sẽ gửi đến đúng handler. Hãy tạo một số ID cơ bản để xác định bảng phân phối tương tự.

namespace cz {
namespace rpc {

//
// Helper code to dispatch a call.
namespace details {
// Handle RPCs with return values
template <typename R>
struct CallHelper {
    template <typename OBJ, typename F, typename P>
    static void impl(OBJ& obj, F f, P&& params, Stream& out) {
        out << callMethod(obj, f, std::move(params));
    }
};

// Handle void RPCs
template <>
struct CallHelper<void> {
    template <typename OBJ, typename F, typename P>
    static void impl(OBJ& obj, F f, P&& params, Stream& out) {
        callMethod(obj, f, std::move(params));
    }
};
}

struct BaseRPCInfo {
    BaseRPCInfo() {}
    virtual ~BaseRPCInfo(){};
    std::string name;
};

class BaseTable {
public:
    BaseTable() {}
    virtual ~BaseTable() {}
    bool isValid(uint32_t rpcid) const {
        return rpcid < m_rpcs.size();
    }
protected:
    std::vector<std::unique_ptr<BaseRPCInfo>> m_rpcs;
};

template <typename T>
class TableImpl : public BaseTable {
public:
    using Type = T;

    struct RPCInfo : public BaseRPCInfo {
        std::function<void(Type&, Stream& in, Stream& out)> dispatcher;
    };

    template <typename F>
    void registerRPC(uint32_t rpcid, const char* name, F f) {
        assert(rpcid == m_rpcs.size());
        auto info = std::make_unique<RPCInfo>();
        info->name = name;
        info->dispatcher = [f](Type& obj, Stream& in, Stream& out) {
            using Traits = FunctionTraits<F>;
            typename Traits::param_tuple params;
            in >> params;
            using R = typename Traits::return_type;
            details::CallHelper<R>::impl(obj, f, std::move(params), out);
        };
        m_rpcs.push_back(std::move(info));
    }
};

template <typename T>
class Table : public TableImpl<T> {
    static_assert(sizeof(T) == 0, "RPC Table not specified for the type.");
};

}  // namespace rpc
}  // namespace cz

The Table template cần phải được chuyên hóa cho class mà ta muốn dùng cho RPC call. Giả sử ta có một Calculator class muốn dùng cho RPC calls:

class Calculator {
public:
    double add(double a, double b) {
        m_ans = a + b;
        return m_ans;
    }
    double sub(double a, double b) {
        m_ans = a - b;
        return m_ans;
    }
    double ans() {
        return m_ans;
    }

private:
    double m_ans = 0;
};

Ta có thể chuyên hóa Table template cho Calculator, để cả client và server đều có thể tham gia:

// Table specialization for Calculator
template <>
class cz::rpc::Table<Calculator> : cz::rpc::TableImpl<Calculator> {
public:
    enum class RPCId { add, sub, ans };

    Table() {
        registerRPC((int)RPCId::add, "add", &Calculator::add);
        registerRPC((int)RPCId::sub, "sub", &Calculator::sub);
        registerRPC((int)RPCId::ans, "ans", &Calculator::ans);
    }

    static const RPCInfo* get(uint32_t rpcid) {
        static Table<Calculator> tbl;
        assert(tbl.isValid(rpcid));
        return static_cast<RPCInfo*>(tbl.m_rpcs[rpcid].get());
    }
};

Với một ID,function `get` sẽ trả dispatcher về đúng method `Calculator`. Rồi ta có thể chuyển `Calculator` instance,  input và output streams sang dispatcher để xử lý mọi thứ còn lại.

Quá trình chuyên hóa khá dài dòng và không thể tránh khỏi sai sót, vì enum và `registerRPC` call phải khớp. Nhưng chúng ta có thể rút ngắn rõ rệt quá trình với một vài macro. Đầu tiên, hãy xem thử một ví dụ dài dòng để thấy cách dùng bảng này:

void testCalculatorTable() {
    // Both the client and server need to have access to the necessary table
    using CalcT = Table<Calculator>;

    //
    // Client sends RPC
    Stream toServer;    
    RPCHeader hdr;
    hdr.bits.rpcid = (int)CalcT::RPCId::add;
    toServer << hdr;
    serializeMethod<decltype(&Calculator::add)>(toServer, 1.0, 9.0);

    //
    // Server receives RPC, and sends back a reply
    Calculator calc;  // object used to receive the RPCs
    toServer >> hdr;
    auto&& info = CalcT::get(hdr.bits.rpcid);
    Stream toClient;  // Stream for the reply
    // Call the desired Calculator function.
    info->dispatcher(calc, toServer, toClient);

    //
    // Client receives a reply
    double r;
    toClient >> r;
    printf("%2.0f\n", r);  // Will print "10"
}

Một lần nữa, ví dụ này khá dài dòng, chỉ để thể hiện code flow. Chúng ta sẽ có ví dụ tối ưu hơn sau.

Vậy, chúng ta đơn giản quá trình chuyên hóa bảng như thế nào? Nếu ta đặt gist của chuyên hóa bảng trong một header không được guard, bạn chỉ cần một vài define theo sau là một include của header (không guard) đó để tạo tương tự.

Ví dụ:

#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    REGISTERRPC(add) \
    REGISTERRPC(sub) \
    REGISTERRPC(ans)
#include "RPCGenerate.h"

Quá đơn giản, đúng không?

`RPCGenerate.h` là một header không được guard, trông như sau:

#ifndef RPCTABLE_CLASS
    #error "Macro RPCTABLE_CLASS needs to be defined"
#endif
#ifndef RPCTABLE_CONTENTS
    #error "Macro RPCTABLE_CONTENTS needs to be defined"
#endif

#define RPCTABLE_TOOMANYRPCS_STRINGIFY(arg) #arg
#define RPCTABLE_TOOMANYRPCS(arg) RPCTABLE_TOOMANYRPCS_STRINGIFY(arg)

template<> class cz::rpc::Table<RPCTABLE_CLASS> : cz::rpc::TableImpl<RPCTABLE_CLASS>
{
public:
    using Type = RPCTABLE_CLASS;
    #define REGISTERRPC(rpc) rpc,
    enum class RPCId {
        RPCTABLE_CONTENTS
        NUMRPCS
    };

    Table()
    {
        static_assert((unsigned)((int)RPCId::NUMRPCS-1)<(1<<Header::kRPCIdBits),
            RPCTABLE_TOOMANYRPCS(Too many RPCs registered for class RPCTABLE_CLASS));
        #undef REGISTERRPC
        #define REGISTERRPC(func) registerRPC((uint32_t)RPCId::func, #func, &Type::func);
        RPCTABLE_CONTENTS
    }

    static const RPCInfo* get(uint32_t rpcid)
    {
        static Table<RPCTABLE_CLASS> tbl;
        assert(tbl.isValid(rpcid));
        return static_cast<RPCInfo*>(tbl.m_rpcs[rpcid].get());
    }
};

#undef REGISTERRPC
#undef RPCTABLE_START
#undef RPCTABLE_END
#undef RPCTABLE_CLASS
#undef RPCTABLE_CONTENTS
#undef RPCTABLE_TOOMANYRPCS_STRINGIFY
#undef RPCTABLE_TOOMANYRPCS

Thêm vào đó, việc chuyên hóa bảng thế này giúp ta hỗ trợ inheritance dễ dàng hơn. Hãy tưởng tượng chúng ta có một `ScientificCalculator` kế thừa từ Calculator:

class ScientificCalculator : public Calculator {
public:
    double sqrt(double a) {
        return std::sqrt(a);
    }
};

Khi đã define riêng biệt các content của Calculator, chúng ta có thể tái sử dụng define này:

// Separately define the Calculator contents so it can be reused
#define RPCTABLE_CALCULATOR_CONTENTS \
    REGISTERRPC(add) \
    REGISTERRPC(sub) \
    REGISTERRPC(ans)

// Calculator table
#define RPCTABLE_CLASS Calculator
#define RPCTABLE_CONTENTS \
    RPCTABLE_CALCULATOR_CONTENTS
#include "RPCGenerate.h"

// ScientificCalculator table
#define RPCTABLE_CLASS ScientificCalculator
#define RPCTABLE_CONTENTS \
    RPCTABLE_CALCULATOR_CONTENTS \
    REGISTERRPC(sqrt)
#include "RPCGenerate.h"

Transport

Chúng ta phải xác định dữ liệu được chuyển đổi như thế nào giữa client và server. Hãy đặt dữ liệu đó vào `Transport` interface class. Interface bị cố ý để rất đơn giản để ứng dụng xác định một custom transport. Tất cả chúng ta cần là method để gửi, nhận, và đóng.

namespace cz {
namespace rpc {

class Transport {
public:
    virtual ~Transport() {}

    // Send one single RPC
    virtual void send(std::vector<char> data) = 0;

    // Receive one single RPC
    // dst : Will contain the data for one single RPC, or empty if no RPC
    // available
    // return: true if the transport is still alive, false if the transport
    // closed
    virtual bool receive(std::vector<char>& dst) = 0;

    // Close connection to the peer
    virtual void close() = 0;
};

}  // namespace rpc
}  // namespace cz

Result

Kết quả RPC phải trông như thế nào? Mỗi khi chúng ta tạo một RPC call, kết quả có thể đi theo 3 hình thức.

Form Meaning
Valid Nhận reply từ server với giá trị kết quả của RPC call
Aborted Connection failed hoặc bị đóng, và chúng ta không nhận được giá trị kết quả
Exception Nhận reply từ server với exception string (RPC call gây exception bên server side)
namespace cz {
namespace rpc {

class Exception : public std::exception {
public:
    Exception(const std::string& msg) : std::exception(msg.c_str()) {}
};

template <typename T>
class Result {
public:
    using Type = T;

    Result() : m_state(State::Aborted) {}

    explicit Result(Type&& val) : m_state(State::Valid), m_val(std::move(val)) {}

    Result(Result&& other) {
        moveFrom(std::move(other));
    }

    Result(const Result& other) {
        copyFrom(other);
    }

    ~Result() {
        destroy();
    }

    Result& operator=(Result&& other) {
        if (this == &other)
            return *this;
        destroy();
        moveFrom(std::move(other));
        return *this;
    }

    // Construction from an exception needs to be separate. so
    // RPCReply<std::string> works.
    // Otherwise we would have no way to tell if constructing from a value, or
    // from an exception
    static Result fromException(std::string ex) {
        Result r;
        r.m_state = State::Exception;
        new (&r.m_ex) std::string(std::move(ex));
        return r;
    }

    template <typename S>
    static Result fromStream(S& s) {
        Type v;
        s >> v;
        return Result(std::move(v));
    };

    bool isValid() const {
        return m_state == State::Valid;
    }
    bool isException() const {
        return m_state == State::Exception;
    };
    bool isAborted() const {
        return m_state == State::Aborted;
    }

    T& get() {
        if (!isValid())
            throw Exception(isException() ? m_ex : "RPC reply was aborted");
        return m_val;
    }

    const T& get() const {
        if (!isValid())
            throw Exception(isException() ? m_ex : "RPC reply was aborted");
        return m_val;
    }

    const std::string& getException() {
        assert(isException());
        return m_ex;
    };

private:
    void destroy() {
        if (m_state == State::Valid)
            m_val.~Type();
        else if (m_state == State::Exception) {
            using String = std::string;
            m_ex.~String();
        }
        m_state = State::Aborted;
    }

    void moveFrom(Result&& other) {
        m_state = other.m_state;
        if (m_state == State::Valid)
            new (&m_val) Type(std::move(other.m_val));
        else if (m_state == State::Exception)
            new (&m_ex) std::string(std::move(other.m_ex));
    }

    void copyFrom(const Result& other) {
        m_state = other.m_state;
        if (m_state == State::Valid)
            new (&m_val) Type(other.m_val);
        else if (m_state == State::Exception)
            new (&m_ex) std::string(other.m_ex);
    }

    enum class State { Valid, Aborted, Exception };

    State m_state;
    union {
        Type m_val;
        std::string m_ex;
    };
};

// void specialization
template <>
class Result<void> {
public:
    Result() : m_state(State::Aborted) {}

    Result(Result&& other) {
        moveFrom(std::move(other));
    }

    Result(const Result& other) {
        copyFrom(other);
    }

    ~Result() {
        destroy();
    }

    Result& operator=(Result&& other) {
        if (this == &other)
            return *this;
        destroy();
        moveFrom(std::move(other));
        return *this;
    }

    static Result fromException(std::string ex) {
        Result r;
        r.m_state = State::Exception;
        new (&r.m_ex) std::string(std::move(ex));
        return r;
    }

    template <typename S>
    static Result fromStream(S& s) {
        Result r;
        r.m_state = State::Valid;
        return r;
    }

    bool isValid() const {
        return m_state == State::Valid;
    }
    bool isException() const {
        return m_state == State::Exception;
    };
    bool isAborted() const {
        return m_state == State::Aborted;
    }

    const std::string& getException() {
        assert(isException());
        return m_ex;
    };

    void get() const {
        if (!isValid())
            throw Exception(isException() ? m_ex : "RPC reply was aborted");
    }

private:
    void destroy() {
        if (m_state == State::Exception) {
            using String = std::string;
            m_ex.~String();
        }
        m_state = State::Aborted;
    }

    void moveFrom(Result&& other) {
        m_state = other.m_state;
        if (m_state == State::Exception)
            new (&m_ex) std::string(std::move(other.m_ex));
    }

    void copyFrom(const Result& other) {
        m_state = other.m_state;
        if (m_state == State::Exception)
            new (&m_ex) std::string(other.m_ex);
    }

    enum class State { Valid, Aborted, Exception };

    State m_state;
    union {
        bool m_dummy;
        std::string m_ex;
    };
};

}  // namespace rpc
}  // namespace cz

Chuyên hóa `Result<void>` rất cần thiết, vì trong trường hợp đó không có kết quả, nhưng caller vẫn muốn biết neus RPC call có được xử lý đúng cách hay không. Mới đầu, tôi từng xem xét sử dụng `Expected<T>` cho RPC reply. Nhưng `Expected<T>` về cơ bản có hai trạng thái (Value hoặc Exception), mà chúng ta lại cần tới 3 (Value, Exception, và Aborted). Ta hay cho rằng Aborted có thể được xem là exceptiom, nhưng từ góc nhìn của client, điều này không phải lúc nào cũng đúng. Trong một số trường hợp bạn sẽ muốn biết một RPC fail vì đóng kết nối, mà không phải vì server phản hồi exception.

Video: Sendo.vn xây dựng kiến trúc hệ thống mở rộng để đáp ứng tăng trưởng 10x mỗi năm như thế nào?

OutProcessor

Chúng ta cần phải theo dõi những RPC call đang diễn ra để user code nhận kết quả khi chúng đến. Ta có thể handle một kết quả theo hai cách. Thông qua handle không đồng bộ (tương tự Asio), hoặc với một future.

Ta cần hai class cho việc này: một outgoing processor và một wrapper cho một RPC call duy nhất. Một class khác (Connection) để buộc các outgoing processor và incoming processor với nhau. Class này sẽ được giới thiệu sau.

namespace cz {
namespace rpc {

class BaseOutProcessor {
public:
    virtual ~BaseOutProcessor() {}

protected:
    template <typename R>
    friend class Call;
    template <typename L, typename R>
    friend struct Connection;

    template <typename F, typename H>
    void commit(Transport& transport, uint32_t rpcid, Stream& data,
                H&& handler) {
        std::unique_lock<std::mutex> lk(m_mtx);
        Header hdr;
        hdr.bits.size = data.writeSize();
        hdr.bits.counter = ++m_replyIdCounter;
        hdr.bits.rpcid = rpcid;
        *reinterpret_cast<Header*>(data.ptr(0)) = hdr;
        m_replies[hdr.key()] = [handler = std::move(handler)](Stream * in,
                                                              Header hdr) {
            using R = typename ParamTraits<
                typename FunctionTraits<F>::return_type>::store_type;
            if (in) {
                if (hdr.bits.success) {
                    handler(Result<R>::fromStream((*in)));
                } else {
                    std::string str;
                    (*in) >> str;
                    handler(Result<R>::fromException(std::move(str)));
                }
            } else {
                // if the stream is nullptr, it means the result is being aborted
                handler(Result<R>());
            }
        };
        lk.unlock();

        transport.send(data.extract());
    }

    void processReply(Stream& in, Header hdr) {
        std::function<void(Stream*, Header)> h;
        {
            std::unique_lock<std::mutex> lk(m_mtx);
            auto it = m_replies.find(hdr.key());
            assert(it != m_replies.end());
            h = std::move(it->second);
            m_replies.erase(it);
        }

        h(&in, hdr);
    }

    void abortReplies() {
        decltype(m_replies) replies;
        {
            std::unique_lock<std::mutex> lk(m_mtx);
            replies = std::move(m_replies);
        }

        for (auto&& r : replies) {
            r.second(nullptr, Header());
        }
    };

    std::mutex m_mtx;
    uint32_t m_replyIdCounter = 0;
    std::unordered_map<uint32_t, std::function<void(Stream*, Header)>>
        m_replies;
};

template <typename T>
class OutProcessor : public BaseOutProcessor {
public:
    using Type = T;

    template <typename F, typename... Args>
    auto call(Transport& transport, uint32_t rpcid, Args&&... args) {
        using Traits = FunctionTraits<F>;
        static_assert(
            std::is_member_function_pointer<F>::value &&
                std::is_base_of<typename Traits::class_type, Type>::value,
            "Not a member function of the wrapped class");
        Call<F> c(*this, transport, rpcid);
        c.serializeParams(std::forward<Args>(args)...);
        return std::move(c);
    }

protected:
};

// Specialization for when there is no outgoing RPC calls
// If we have no outgoing RPC calls, receiving a reply is therefore an error.
template <>
class OutProcessor<void> {
public:
    OutProcessor() {}
    void processReply(Stream&, Header) {
        assert(0 && "Incoming replies not allowed for OutProcessor<void>");
    }
    void abortReplies() {}
};

}  // namespace rpc
}  // namespace cz

Nên nhớ `OutProcessor<T>` không cần một reference/pointer đến object của T. Nó chỉ cần biết type mà ta gửi RPC đến, để biết được phải dùng `Table<T>`.

Đây là ví dụ sử dụng OutProcessor:

//
// "trp" is a transport that sends data to a "Calculator" server
void testOutProcessor(Transport& trp) {
    // A processor that calls RPCs on a "Calculator" server
    OutProcessor<Calculator> outPrc;

    // Handle with an asynchronous handler
    outPrc.call<decltype(&Calculator::add)>(
              trp, (int)Table<Calculator>::RPCId::add, 1.0, 2.0)
        .async([](Result<double> res) {
            printf("%2.0f\n", res.get());  // prints '3'
        });

    // Handle with a future
    Result<double> res = outPrc.call<decltype(&Calculator::add)>(
                        trp, (int)Table<Calculator>::RPCId::add, 1.0, 3.0)
                  .ft().get();
    printf("%2.0f\n", res.get());  // prints '4'
}

Một lần nữa, hơi quá dài dòng, vì tôi chưa giới thiệu tất cả code để áp dụng tối ưu. Nhưng chí ít, ta đã thấy được interface `OutProcessor<T>` và `Call` làm việc như thế nào. Thực thi `std::future` build đơn giản trên thực thi không đồng bộ.

InProcessor

Giờ đây ta có thể gửi một RPC và đợi kết quả, hãy xem thử ta cần gì ở bên kia. Phải làm gì khi server nhận được một RPC call.

Hãy tạo class `InProcessor<T>`. Trái với `OutProcessor<T>`, `InProcessor<T>` cần phải giữ một reference tới một object thuộc kiểu T. Điều này sảy ra khi nhận được RPC, nó có thể gọi requested method lên object đó, và gửi kết quả trở lại client.

namespace cz {
namespace rpc {

class BaseInProcessor {
public:
    virtual ~BaseInProcessor() {}
};

template <typename T>
class InProcessor : public BaseInProcessor {
public:
    using Type = T;
    InProcessor(Type* obj, bool doVoidReplies = true)
        : m_obj(*obj), m_voidReplies(doVoidReplies) {}

    void processCall(Transport& transport, Stream& in, Header hdr) {
        Stream out;
        // Reuse the header as the header for the reply, so we keep the counter
        // and rpcid
        hdr.bits.size = 0;
        hdr.bits.isReply = true;
        hdr.bits.success = true;

        auto&& info = Table<Type>::get(hdr.bits.rpcid);

#if CZRPC_CATCH_EXCEPTIONS
        try {
#endif
            out << hdr;  // Reserve space for the header
            info->dispatcher(m_obj, in, out);
#if CZRPC_CATCH_EXCEPTIONS
        } catch (std::exception& e) {
            out.clear();
            out << hdr;  // Reserve space for the header
            hdr.bits.success = false;
            out << e.what();
        }
#endif

        if (m_voidReplies || (out.writeSize() > sizeof(hdr))) {
            hdr.bits.size = out.writeSize();
            *reinterpret_cast<Header*>(out.ptr(0)) = hdr;
            transport.send(out.extract());
        }
    }

protected:
    Type& m_obj;
    bool m_voidReplies = false;
};

template <>
class InProcessor<void> {
public:
    InProcessor(void*) {}
    void processCall(Transport&, Stream&, Header) {
        assert(0 && "Incoming RPC not allowed for void local type");
    }
};

}  // namespace rpc
}  // namespace cz

Define `CZRPC_CATCH_EXCEPTIONS` cho phép chúng ta tweak nếu ta muốn exception phía server được chuyển sang phía client.

Nếu việc sử dụng `InProcessor<T>` (và `Table<T>`) cho phép call RPC lên object (không biết gì về RPC hoặc netwrk). Ví dụ như, hay xem xét giả dụ sau:

oid calculatorServer() {
    // The object we want to use for RPC calls
    Calculator calc;
    // The server processor. It will call the appropriate methods on 'calc' when
    // an RPC is received
    InProcessor<Calculator> serverProcessor(&calc);
    while (true) {
        // calls to serverProcessor::processCall whenever there is data
    }
}

Object `Calculator` được dùng cho RPC không biết gì về RPC. `InProcessor<Calculator>` sẽ đảm nhiệm mọi nhiệm vụ liên quan. Từ đó ta không thể sử dụng các class bên thức ba cho PRC. Trong một số trường hợp, chung ta muốn class dùng cho RPC biết biết về RPC và/hoặc network. Ví dụ như, nếu bạn đang tạo một chat system, bạn sẽ khiến client gửi message (RPC call) đến server. Server cần biết client được kết nối đến thứ gì, để có thể truyền phát message.

Connection

Ta giờ đây đã có thể gửi nhận RPC. dù API có hơi dài một chút. Những template class `OutProcessor<T>` và `InProcessor<T>` sẽ xử lý những sự kiện sảy ra với data tại cả hai đầu kết nối. Vậy, hiện ta cần chính điều này. Một `Connection` để buộc mọi thức cần để gửi nhận dữ liệu, và đơn giản là API, về một chỗ.

namespace cz {
namespace rpc {

struct BaseConnection {
    virtual ~BaseConnection() {}

    //! Process any incoming RPCs or replies
    // Return true if the connection is still alive, false otherwise
    virtual bool process() = 0;
};

template <typename LOCAL, typename REMOTE>
struct Connection : public BaseConnection {
    using Local = LOCAL;
    using Remote = REMOTE;
    using ThisType = Connection<Local, Remote>;
    Connection(Local* localObj, std::shared_ptr<Transport> transport)
        : localPrc(localObj), transport(std::move(transport)) {}

    template <typename F, typename... Args>
    auto call(Transport& transport, uint32_t rpcid, Args&&... args) {
        return remotePrc.template call<F>(transport, rpcid,
                                          std::forward<Args>(args)...);
    }

    static ThisType* getCurrent() {
        auto it = Callstack<ThisType>::begin();
        return (*it) == nullptr ? nullptr : (*it)->getKey();
    }

    virtual bool process() override {
        // Place a callstack marker, so other code can detect we are serving an
        // RPC
        typename Callstack<ThisType>::Context ctx(this);
        std::vector<char> data;
        while (true) {
            if (!transport->receive(data)) {
                // Transport is closed
                remotePrc.abortReplies();
                return false;
            }

            if (data.size() == 0)
                return true;  // No more pending data to process

            Header hdr;
            Stream in(std::move(data));
            in >> hdr;

            if (hdr.bits.isReply) {
                remotePrc.processReply(in, hdr);
            } else {
                localPrc.processCall(*transport, in, hdr);
            }
        }
    }

    InProcessor<Local> localPrc;
    OutProcessor<Remote> remotePrc;
    std::shared_ptr<Transport> transport;
};

}  // namespace rpc
}  // namespace cz

Từ đây ta có output processor, the input processor, và the transport. Để user code có thể xác định liệu mó có đang phục vụ RPC không. nó sử dụng một class Callstack. Class cho phép tạo RPC/network aware code nếu cần thiết, giống server class.

Vậy, ta đơn giản hóa API bằng cách nào? Vì `Connection<T>` có mọi thứ ta cần, một macro dưới dạng paramether của connection object, một function name và parameter, xử lý mọi thứ, bao gồm cả type check để nó không compile nếu là call không hợp lệ.

#define CZRPC_CALL(con, func, ...)                                        \
    (con).call<decltype(&std::decay<decltype(con)>::type::Remote::func)>( \
        *(con).transport,                                                 \
        (uint32_t)cz::rpc::Table<                                         \
            std::decay<decltype(con)>::type::Remote>::RPCId::func,        \
        ##__VA_ARGS__)

Khi dùng marcro này, cú pháp RPC call sẽ trở nên cực kỳ đơn giản. Hãy xe, thử client code ví dụ dưới đây:

// Some class to use for RPC calls
class MagicSauce {
public:
    int func1(int a, int b) {
        return a + b;
    }

    int func2(int a, int b) {
        return a + b;
    }
};

// Define RPC table for MagicSauce
#define RPCTABLE_CLASS MagicSauce
#define RPCTABLE_CONTENTS REGISTERRPC(func1)
#include "RPCGenerate.h"

// 'trp' is a fully functional transport
void test_Connection(std::shared_ptr<Transport> trp) {
    Connection<void, MagicSauce> con(nullptr, trp);

    // Doesn't compile : Invalid number of parameters
    // CZRPC_CALL(con, func1, 1);

    // Doesn't compile : Wrong type of parameters
    // CZRPC_CALL(con, func1, 1, "hello");

    // Doesn't compile: func3 is not a MagicSauce method
    // CZRPC_CALL(con, func3, 1, 2);

    // Doesn't compile: func2 is a method of MagicSauce, but not registered as
    // RPC
    // CZRPC_CALL(con, func2, 1, 2);

    // Compiles fine, since everything is valid
    CZRPC_CALL(con, func1, 1, 2).async([](Result<int> res) {
            printf("%d\n", res.get());  // print '3'
        });
}

Bạn có để ý là `void` và `nullptr` được sử dụng khi tạo kết nối với `Connection<void`, `MagicSauce> con(nullptr, trp);`? Thao tác này sẽ điều tiết bidirectional RPC (server cũng có thể call RPC lên một client). Trong trường hợp này, chúng ta không trong đợi vào client side RPC, vậy nên client side `Connection` object không có một local object để call RPC.

Một ví dụ đơn giản (nhưng không hoạt động) của bidirectional RPC:

class ChatClient;

class ChatServer {
public:
    // Called by clients to post new messages
    void msg(const char* msg);
    void addNewClient(std::shared_ptr<Transport> trp);

private:
    // Connection specifies both a LOCAL, and REMOTE object types
    std::vector<std::unique_ptr<Connection<ChatServer, ChatClient>>> m_clients;
};

#define RPCTABLE_CLASS ChatServer
#define RPCTABLE_CONTENTS REGISTERRPC(msg)
#include "RPCGenerate.h"

class ChatClient {
public:
    void onMsg(const char* msg);
};

#define RPCTABLE_CLASS ChatClient
#define RPCTABLE_CONTENTS REGISTERRPC(onMsg)
#include "RPCGenerate.h"

void ChatServer::msg(const char* msg) {
    // Simply broadcast the message to all clients
    for (auto&& c : m_clients) {
        CZRPC_CALL(*c, onMsg, msg);
    }
}
void ChatServer::addNewClient(std::shared_ptr<Transport> trp) {
    auto con = std::make_unique<Connection<ChatServer, ChatClient>>(this, trp);
    m_clients.push_back(std::move(con));
}

void ChatClient::onMsg(const char* msg) {
    printf("%s\n", msg);
}

void test_ChatServer() {
    ChatServer server;
    while (true) {
        // Wait for connections, and call ChatServer::addClient
    }
}

// 'trp' is some fully functional transport connected to the ChatServer
void test_ChatClient(std::shared_ptr<Transport> trp) {
    ChatClient client;
    // In this case, we have a client side object to answer RPCs
    Connection<ChatClient, ChatServer> con(&client, trp);
    while (true) {
        // call the 'msg' RPC whenever the user types something, like this:
        CZRPC_CALL(con, msg, "some message");

        // The server will call our client 'onMsg' when other clients send a
        // message
    }
}

Các parameter của `Connection` template trên server và client được đảo ngược. Khi nhận được dữ liệu, object `Connection` sẽ forward xử lý đến `InProcessor` nếu là một incoming RPC call, hoặc đến `OutProcessor` nếu đó là reply đến outgoing RPC call trước đó. Data flow của bidirectional RPC trông như thế này:

Improvements

Có một số phần được cố ý không đưa vào framework, để từ đó ứng dụng có thể quyết định thế nào là tốt nhất. Ví dụ:

  • Transport initialization
    • Giao diện transport rất đơn giản, nên nó không quá chú trọng vào hướng khởi tạo cụ thể hoặc xác định incoming data. Việc cung cấp một transport hiệu quả đến class `Connecttion` hoàn toàn phụ thuộc vào ứng dụng. Đây cũng là lý do tại sao tôi tránh hiển thị transport initialization, vì tôi phải trình bày một transport implementation hoạt động hoàn chỉnh.
    • Tại thời điểm viết bài, repo mã nguồn có một transport implementation dùng Boost Asio (hoặc Asio biệt lập)
  • Xác định disconnection
    • Như với initialization, có những đoạn code để xử lý và xác định shutdown.Ứng dụng sẽ có nhiệm vụ quyết định thao tác như thế nào với bất cứ custom implementation nó cung cấp.
  • Và nhiều hơn nữa

TopDev via gamasutra

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Flutter khác biệt gì so với những công nghệ đương thời?

flutter-khac-biet-gi-so-voi-nguoi-anh-android

Flutter là gì?

Flutter là mobile UI framework của Google để tạo ra các giao diện native chất lượng cao trên iOSAndroid trong khoảng thời gian ngắn. Flutter hoạt động với source code có sẵn, được sử dụng bởi các nhà phát triển và các tổ chức trên khắp thế giới, đồng thời nó open-source và miễn phí.

Clip giới thiệu về Flutter:

Sự vượt trội của Flutter

Flutter là một công cụ mới được cung cấp bởi Google cho phép các nhà phát triển xây dựng các ứng dụng đa nền tảng có thể được thực hiện trong các hệ thống khác nhau chẳng hạn như Android hay iOS chỉ với một codebase chung.

Công cụ này được được xây dựng trong C và C ++ và cung cấp một cơ chế rendering 2D, một funtional-reactive framework là React-inspired, và một tập hợp các Material Design widget. Nó hiện đang được distribute bản alpha: version 0.0.20, tuy vậy giai đoạn đầu của nó đã được cho phép để tạo ra interfacing phức tạp, thực hiện kết nối mạng và thậm chí quản lý tập tin.

Cách tiếp cận Flutter’s là khác nhau từ các solution khác, ví dụ Cordova chạy trên một WebView là HTML, CSS và Javascript. Không giống như những công cụ này, nó chỉ sử dụng Dart như một ngôn ngữ lập trình duy nhất. Dart là khá dễ dàng để tìm hiểu và nếu bạn có kiến thức Java, 75% của công việc được gần như hoàn tất và làm quen với Dart sẽ chỉ mất một vài ngày.

  Flutter Vs. React Native - Nên chọn framework nào?

Tại sao nên sử dụng Flutter?

Phát triển ứng dụng nhanh chóng: Tính năng hot reload của Flutter giúp bạn nhanh chóng và dễ dàng thử nghiệm, xây dựng giao diện người dùng, thêm tính năng và sửa lỗi nhanh hơn. Trải nghiệm tải lại lần thứ hai, mà không làm mất trạng thái, trên emulator, simulator và device cho iOS và Android.

UI đẹp và biểu cảm: Thỏa mãn người dùng của bạn với các widget built-in đẹp mắt của Flutter theo Material Design và Cupertino (iOS-flavor), các API chuyển động phong phú, scroll tự nhiên mượt mà và tự nhận thức được nền tảng.

flutter

Framework hiện đại và reactive: Dễ dàng tạo giao diện người dùng của bạn với framework hiện đại, reactive của Flutter và tập hợp các platform, layout và widget phong phú. Giải quyết các thách thức giao diện người dùng khó khăn của bạn với các API mạnh mẽ và linh hoạt cho 2D, animation, gesture, hiệu ứng và hơn thế nữa.

Truy cập các tính năng và SDK native: Làm cho ứng dụng của bạn trở nên sống động với API của platform, SDK của bên thứ ba và native code. Flutter cho phép bạn sử dụng lại mã Java, Swift và ObjC hiện tại của mình và truy cập các tính năng và SDK native trên iOS và Android.

Phát triển ứng dụng thống nhất: Flutter có các công cụ và thư viện để giúp bạn dễ dàng đưa ý tưởng của mình vào cuộc sống trên iOS và Android. Nếu bạn chưa có kinh nghiệm phát triển trên thiết bị di động, thì Flutter là một cách dễ dàng và nhanh chóng để xây dựng các ứng dụng di động tuyệt đẹp. Nếu bạn là một nhà phát triển iOS hoặc Android có kinh nghiệm, bạn có thể sử dụng Flutter cho các View của bạn và tận dụng nhiều code Java / Kotlin / ObjC / Swift hiện có của bạn.

Mẹo quy ước tên cho CSS giúp bạn rút ngắn 2/3 thời gian khi debug!

meo-quy-uoc-ten-cho-css-giup-ban-rut-ngan-23-thoi-gian-khi-debug

Tác giả: Emmanuel Ohans

Tôi đã nghe không ít developer nói rằng họ ghét CSS. Với kinh nghiệm của mình, đây là kết quả cho việc không dành thời gian để học CSS.

CSS không phải là “ngôn ngữ” đẹp nhất, nhưng nó đã thành công trong việc làm front-end cho các website trong hơn 20 năm nay. Tuy nhiên, khi bạn viết nhiều CSS, bạn sẽ nhận ra được thấy một nhược điểm lớn. Quá khó để developer có thể maintain CSS.

Các CSS tệ sẽ nhanh chóng trở thành một cơn ác mộng đối với các lập trình viên. Dưới đây là một số quy ước tên sẽ giúp bạn giảm vô số giờ để debug.

Có thể bạn muốn xem:

  Responsive HTML bằng một dòng CSS
  Roadmap Frontend Developer - "Con đường tắt" để trở thành cao thủ Frontend Developer

Sử dụng dấu phân cách “-“

Nếu bạn sử dụng nhiều JavaScript

var redBox = document.getElementById('...')

Vấn đề là cách đặt tên này không phù hợp với CSS.

Đừng bao giờ:

.redBox {
  border: 1px solid red;
}

Thay vì vậy, hãy làm như thế này:

.red-box {
   border: 1px solid red;
}

Đây là một tiêu chuẩn đặt tên cho CSS. Và điều này được cho là dễ đọc hơn.

Ngoài ra, nó cũng phù hợp với thuộc tính của CSS.

// Correct
.some-class {
   font-weight: 10em
}
// Wrong
.some-class {
   fontWeight: 10em
}

Qui ước đặt tên BEM

Các team có cách tiếp cận khác nhau để viết CSS selector. Một số nhóm sử dụng dấu phân cách, trong khi một số khác lại thích sử dụng một cách đặt tên có cấu trúc hơn gọi là BEM.

Nhìn chung, có 3 vấn đề mà qui tắc này giải quyết:

  1. Để biết 1 selector làm cái gì, chỉ cần nhìn vào tên của nó
  2. Để có một ý tưởng về nơi mà một selector được sử dụng, chỉ cần nhìn vào nó
  3. Để biết mối quan hệ giữa các các class name, chỉ cần nhìn vào chúng

Bạn đã bao giờ nhìn thấy class name được viết như sau:

.nav--secondary {
  ...
}
.nav__header {
  ...
}

Đó là quy tắc BEM.

Video: Tối ưu Front-end để web của bạn load dưới 5 giây

Giải thích BEM

BEM cố gắng phân chia giao diện người dùng tổng thể thành các component nhỏ có thể tái sử dụng.

Hãy quan sát hình ảnh dưới đây:

Hình này biểu diễn một component, chẳng hạn như một khối thiết kế.
Bạn có thể dễ đoán được B trong BEM là viết tắt của ‘Block’.
Trong thực tế, ‘block’ này đại diện cho điều hướng trang, tiêu đề, chân trang hoặc bất kỳ khối thiết kế khác.
Theo thực tiễn được giải thích ở trên, một class name lý tưởng cho component này sẽ là stick-man.
Component nên theo kiểu sau:
.stick-man {
  
 }

Chúng ta đã sử dụng các chuỗi giới hạn ở đây. Thật tuyệt vời!

E là viết tắt của “Elements”

E trong ‘BEM’ là viết tắt của Elements.

Khối thiết kế hiếm khi bị cô lập.

Ví dụ, stick-man phải có đầu, hai cánh tay và chân.

head, feetarms là tất cả các element bên trong component. Chúng có thể được xem như các component con, nghĩa là con của component bố mẹ nó.
Sử dụng quy tắc BEM, các element  của class name được lấy ra bằng cách thêm hai dấu gạch dưới, tiếp theo là tên phần tử.
Ví dụ:
.stick-man__head {
}
.stick-man__arms {
}
.stick-man__feet {
}

M là “Modifiers”

M trong ‘BEM’ viết tắt của Modifiers.

Điều gì sẽ xảy ra nếu stick-man ông bị sửa đổi và chúng ta có một stick-man có màu xanh hoặc đỏ?

Thực tế, đây có thể là nút đỏ hoặc nút màu xanh. Đây là những sửa đổi của component được đề cập.

Sử dụng BEM, các modifier class name được lấy ra bằng cách thêm hai dấu nối sau tên của phần tử.

Ví dụ:

.stick-man--blue {
}
.stick-man--red {
}

Ví dụ cuối cùng cho thấy các component cha mẹ đang được sửa đổi. Nhưng không phải lúc nào cũng như vậy.

Điều gì sẽ xảy ra nếu chúng ta có những stick-man có kích cỡ đầu khác nhau?

Lúc này các element đã được sửa đổi. Hãy lưu ý, element là một component con trong khối  tổng thể.

Các .stick-man đại diện cho Block , .stick-man__head element

Như đã thấy trong ví dụ ở trên, dấu gạch nối đôi cũng có thể được sử dụng như sau:

.stick-man__head--small {
}
.stick-man__head--big {
}

Hãy lưu ý việc sử dụng các dấu gạch nối đôi trong ví dụ trên. Điều này biểu thị một modifier.

Đây là điều cơ bản về cách quy tắc BEM hiệu quả như thế nào.

Cá nhân tôi có xu hướng chỉ sử dụng các class name dùng phân cách gạch ngang cho các dự án đơn giản và BEM cho các giao diện người dùng có liên quan.

Tại sao sử dụng các quy ước đặt tên?

Đặt tên là một việc cực kì khó. Chúng tôi đang cố gắng làm mọi việc dễ dàng hơn và tiết kiệm thời gian cho chúng tôi trong tương lai với code có thể duy trì nhiều hơn.

Đặt tên đúng cách trong CSS sẽ làm cho code của bạn dễ đọc và dễ dàng hơn.

Đặt đúng tên trong CSS sẽ giúp bạn dễ đọc và duy trì code hơn.

Nếu bạn chọn sử dụng quy tắc BEM, nó sẽ trở nên dễ dàng hơn để xem mối quan hệ giữa các component của bạn/khối chỉ bằng cách nhìn vào đánh dấu.

Đặt tên CSS với JavaScript Hooks

VD ta được chuyển giao code HTML trông như sau:

<div class="siteNavigation">
</div>

ta đã đọc bài này và nhận ra rằng đây không phải là cách tốt nhất để đặt tên cho những thứ trong CSS. Vì vậy, ta tiếp tục code và tái cấu trúc các codebase trước:

<div class="site-navigation">
</div>

Một nơi nào đó trong JavaScript code, có sử dụng class mà ta chưa rename, siteNavigation:

//the Javasript code
const nav = document.querySelector('.siteNavigation')

Vì vậy, với sự thay đổi class name, biến nav biến thành null.

Để ngăn chặn các trường hợp này, developer đã đưa ra các chiến lược khác nhau.

1.Sử dụng js- class names

Một cách để giảm thiểu các lỗi như vậy là sử dụng class name js-* để biểu thị mối quan hệ với DOM element đang được đề cập.

Ví dụ:

<div class="site-navigation js-site-navigation">
</div>

Và JavaScript code:

//the Javasript code
const nav = document.querySelector('.js-site-navigation')

Theo một quy ước, bất kỳ ai nhìn thấy js-site-navigation class name sẽ hiểu rằng có mối liên hệ với DOM element đó trong mã JavaScript.

2. Sử dụng thuộc tính Rel

Bạn có nhận ra điều này?

<link rel="stylesheet" type="text/css" href="main.css">

Về cơ bản, thuộc tính rel xác định mối quan hệ mà tài nguyên đã liên kết đến resource mà nó được tham chiếu.

Trong ví dụ trước, những người đề xướng kỹ thuật sẽ làm việc này:

<div class="site-navigation" rel="js-site-navigation">
</div>

Và với JavaScript:

const nav = document.querySelector("[rel='js-site-navigation']")

Tôi có nghi ngờ về kỹ thuật này, nhưng bạn có thể bỏ qua nó trong một số codebases. Yêu cầu ở đây là “có một mối quan hệ với Javascript, vì vậy tôi sử dụng thuộc tính rel để biểu thị điều đó”.

Web là một nơi lớn với rất nhiều “phương pháp” khác nhau để giải quyết cùng một vấn đề.

3. Không sử dụng data attribute

Một số developer sử dụng các data attributes như JavaScript Hooks. Điều này là không đúng. Theo định nghĩa, data attributes được sử dụng để lưu trữ dữ liệu tùy chỉnh.

Tip: Thêm các CSS comments

Điều này không liên quan gì đến các quy ước đặt tên, nhưng cũng sẽ giúp bạn tiết kiệm được nhiều thời gian.

Trong khi rất nhiều nhà phát triển web cố gắng không viết các bình luận JavaScript hoặc viết đại loại, tôi nghĩ bạn nên viết comment cho CSS.

Vì CSS không phải là “ngôn ngữ” thân thiện nhất, nếu có comment chi tiết rõ ràng thì bạn sẽ tiết kiệm thời gian khi bạn hoặc ai đó sau này xem lại code của mình.

Tham khảo thêm các vị trí tuyển dụng lập trình CSS lương cao cho bạn

Bài viết gốc được đăng tải tại Freecodecamp

10 tài liệu lập trình Android miễn phí từ cơ bản đến nâng cao

TopDev chọn lọc và giới thiệu các tài liệu lập trình Android miễn phí từ cơ bản đến nâng cao cùng những công cụ dành cho các bạn muốn tìm hiểu và bắt đầu lập trình Android, cũng như muốn nâng cao “tay nghề” và dấn thân vào con đường lập trình Android chuyên nghiệp.

Khám phá Top các vị trí lập trình Android hấp dẫn

  1. Android Programming for Beginners
Đây là tài liệu Android dành cho những người mới sử dụng JAVA và lập trình Android. Tác giả cung cấp hơn 40 ứng dụng nhỏ trong suốt cuốn sách để đi cùng với lời giải thích đơn giản và rõ ràng về các chủ đề. Từ Android Studio đến JAVA đến vòng đời sản phẩm, cuốn sách này bao gồm tất cả các khái niệm cơ bản bạn cần nắm để bắt đầu xây dựng ứng dụng Android đầu tiên của mình. https://topdev.vn/s/LOwqpcUg

2. Head First Android Development

Tuyển tập seri Head First đã mang đến cuốn sách tuyệt vời cho các lập trình viên. Head First Android Development thể hiện cách tiếp cận độc đáo, hướng dẫn bằng hình ảnh để việc học lập trình Android trở nên thú vị và hấp dẫn. Ngay cả đối với người mới bắt đầu làm quen Android, cuốn sách này sẽ giúp bạn nắm bắt cách xây dựng ứng dụng Android đầu tiên của mình một cách nhanh chóng. https://topdev.vn/s/ImtUrCX0

3. The Android Developer’s Cookbook – Building Applications with the Android SDK

Quyển sách dành cho các bạn muốn bắt tay vào xây dựng app trên nền tảng Android. Cập nhật các chương mới về phát triển giao diện người dùng và luồng nâng cao, thanh toán trong ứng dụng,…cùng với các kỹ thuật mới truy cập phần cứng NFC đến sử dụng Google Cloud Messaging. Bạn sẽ học các kỹ thuật thực hành tốt nhất để giải quyết hiệu quả các vấn đề phổ biến và để tránh những cạm bẫy trong toàn bộ vòng đời phát triển https://topdev.vn/s/cZtmq7hu

4. Android Programming: The Big Nerd Ranch Guide (2nd Edition)

Nếu bạn là người đã có kinh nghiệm lập trình JAVA, đây là một cuốn sách tuyệt vời cho việc học lập trình Android. Dựa trên khóa học Android Bootcamp trong một tuần được tài trợ bởi Big Nerd Ranch, cuốn sách này cung cấp những giải thích một cách toàn diện, thực tiễn và súc tích về các khái niệm, API trong lập trình Android.

https://topdev.vn/s/0arpSF9J

5. Android Programming: Pushing the Limits

Đây là một lựa chọn tuyệt vời khác cho lập trình viên Android đang tìm kiếm những thứ “nặng đô” hơn. Cuốn sách nhằm mục đích thúc đẩy các ranh giới lập trình Android với nhiều mẹo, thủ thuật và các kỹ thuật mà đa số chưa biết hoặc chưa được tận dụng. Đây sẽ giúp bạn trở thành lập trình viên Android cao cấp hơn. android-programing

6. Advanced Android Application Development

Đối với lập trình viên Android dày dạn kinh nghiệm đang tìm kiếm các chủ đề nâng cao và chuyên sâu hơn, đây là một lựa chọn tuyệt vời. Cuốn sách cung cấp các kỹ thuật lập trình Android cao cấp để giúp các lập trình viên xây dựng các ứng dụng ở mức chuyên nghiệp hơn. Ngoài ra, Advanced Android Application Development cung cấp cho nhà phát triển một bộ hướng dẫn toàn diện chắc chắn sẽ có ích. https://topdev.vn/s/Hy3s6oPv

7. Raspberry Pi Android Projects

Quyển sách cung cấp các kiến thức quản lý hầu hết các chức năng cơ bản của Raspberry Pi từ điện thoại Android của bạn. Sử dụng các dự án được tạo trong cuốn sách này để phát triển các dự án thú vị hơn nữa trong tương lai. Trải nghiệm học tập dựa trên dự án để giúp bạn khám phá những cách tuyệt vời để kết hợp sức mạnh của Android và Raspberry Pi.

https://topdev.vn/s/Io76Eeju

8. Android Recipes

Trong Android Recipes, bạn sẽ tìm thấy các ví dụ mã trực tiếp. Nó cung cấp lời khuyên thực tế sẽ giúp bạn hoàn thành công việc một cách nhanh chóng và tốt đẹp. Điều này có thể giúp bạn tiết kiệm rất nhiều công việc so với việc tạo một dự án từ đầu! https://topdev.vn/s/6m5t6pHd

9.30+ công cụ phát triển ứng dụng Android chuyên nghiệp ( Phần 1)

Tập hợp một số công cụ tốt nhất để nâng cao hiệu suất phát triển và xây dựng các ứng dụng Android. Những công cụ này đã giúp các nhà lập trình tiết kiệm rất nhiều thời gian quý giá từ cuộc sống hàng ngày và cũng như tạo các ứng dụng tốt hơn và chất lượng được nâng lên đáng kể.

https://topdev.vn/s/3qp9C65s

10. 30+ công cụ phát triển ứng dụng Android chuyên nghiệp ( Phần 2)

Là sự tiếp nối của phần 1, ở phần 2 tác giả đề cập những công cụ tiếp theo nhằm hỗ trợ đắc lực trong nâng cao hiệu suất phát triển và xây dựng các ứng dụng Android

Tìm việc làm các vị trí lập trình Android hấp dẫn tại TopDev.

Cách viết CV giúp lập trình viên ghi điểm với nhà tuyển dụng

Cách viết CV dành cho Software Developer

Một CV tốt, đồng nghĩa với cơ hội bạn được nhà tuyển dụng để mắt đến càng cao. Để nâng cao khả năng trúng tuyển thì một bản CV chuyên dụng của từng ngành là điều không thể thiếu, nhất là đối với ngành đòi hỏi chuyên môn cao như công nghệ thông tin. Để viết CV xin việc IT thì có rất nhiều cách, và một CV IT gây ấn tượng thông thường có các đề mục cơ bản sau:

  1. Thông tin cá nhân: Liệt kê đơn giản họ tên, năm sinh, địa chỉ, email, số ĐT kèm ảnh đại diện nên rõ mặt, nghiêm túc, chất lượng rõ nét, không nên là ảnh selfie. Email cũng cần nghiêm túc và tốt nhất là bằng tên thật của bạn để thể hiện sự chuyên nghiệp.
  2. Mục tiêu nghề nghiệp: Nêu rõ định hướng của bạn trong con đường nghề nghiệp của mình. Hãy tóm tắt trong vòng 2-3 câu mục tiêu ngắn hạn/dài hạn của bạn đối với công việc đang ứng tuyển. Với mục này nhà tuyển dụng có thể đánh giá được phần nào năng lực cũng như tầm nhìn, và quyết định xem có nên đọc tiếp CV của bạn hay không.
  3. Kỹ năng: Liệt kê những gì bạn biết và/hoặc có kinh nghiệm và nêu chính xác mức độ hiểu biết của bạn trong từng mục. Phần này cũng quan trọng không kém trong CV ngành công nghệ thông tin.
  4. Kinh nghiệm làm việc: Tóm tắt, liệt kê những dự án, công ty mà bạn đã từng làm việc, kèm chức vụ và sắp xếp theo trình tự thời gian từ mới nhất đến cũ nhất. Đây có thể nói là phần quan trọng nhất của một CV IT.
  5. Thành tựu: Liệt kê thành tựu cá nhân trong các dự án đó. Thành tựu ở đây có thể là những gì bạn tự hào rằng mình đã làm được cho dự án, những điều bạn nhận ra và cải thiện được cho bản thân nhờ dự án.
  6. Thông tin khác: Bằng cấp, giải thưởng, dự án riêng (nếu có)

Tạo CV IT online, chuẩn ATS miễn phí trên TopDev

Tìm việc chỉ phụ thuộc vào may mắn là một quan niệm sai lầm. Sự chuẩn bị cẩn thận và đánh giá đúng khả năng của bản thân quyết định rất lớn đến việc thành công ứng tuyển của bạn. Để bạn không phải lo lắng quá nhiều về câu hỏi làm sao để có 1 CV IT chuẩn? TopDev đã tạo sẵn cho bạn những mẫu CV IT cực đẹp mà đơn giản kèm theo thông tin đầy đủ theo tiêu chuẩn. Bạn chỉ cần đơn giản là vào công cụ tạo CV online đáng tin cậy của TopDev.

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Tổng hợp các mẫu CV IT đẹp và cách viết CV xịn dành cho lập trình viên

Mẫu CV

Tổng hợp các mẫu CV IT đẹp – chuẩn dành cho các bạn lập trình viên Backend, Frontend, Fullstack, iOS, Android, PHP, .NET, Java… từ level Fresher cho đến Senior, Leader.

Giới thiệu

Viết CV luôn là một vấn đề nan giải cho bất kỳ ai. Tuy nhiên bạn chỉ cần dành ra 5 phút để tham khảo các mẫu CV IT chuẩn được TopDev thiết kế dành riêng cho lập trình viên, dựa trên hơn 1.000 mẫu CV Developer nổi bật trên thế giới, ứng tuyển thành công các vị trí tại các tập đoàn công nghệ lớn Microsoft, Google, Amazon…

Bạn sẽ biết cách làm thế nào để có một CV đúng, chuẩn và phù hợp với từng vị trí công việc cụ thể đối với ngành công nghệ thông tin, chọn lọc các thông tin tiêu chuẩn và cấu trúc thống nhất giúp Nhà tuyển dụng dễ dàng đánh giá kinh nghiệm và Tech stack của bạn.

  Cách viết CV giúp lập trình viên ghi điểm với nhà tuyển dụng

Hiện tại các mẫu cv này có hỗ trợ cả tiếng Anh lẫn tiếng Việt với các format chuẩn cho bạn sự lựa chọn tốt nhất.

>>> Xem thêm: Mẫu CV IT tiếng Anh hấp dẫn nhà tuyển dụng

Ngoài ra, việc có CV tốt cũng giúp cho bạn có được nhiều cơ hội tốt hơn trong tương lai, hãy nhớ rằng, trong CV phải luôn có đủ những thông tin sau đây.

  • Thông tin cá nhân: Liệt kê đơn giản họ tên, năm sinh, địa chỉ, email, số điện thoại kèm ảnh đại diện nên rõ mặt, nghiêm túc, chất lượng rõ nét, không nên là ảnh selfie. Email cũng cần nghiêm túc và tốt nhất là bằng tên thật của bạn để thể hiện sự chuyên nghiệp.
  • Mục tiêu nghề nghiệp: Nêu rõ định hướng của bạn trong con đường nghề nghiệp của mình. Hãy tóm tắt trong vòng 2-3 câu mục tiêu ngắn hạn/dài hạn của bạn đối với công việc đang ứng tuyển.

Với mục này nhà tuyển dụng có thể đánh giá được phần nào năng lực cũng như tầm nhìn, và quyết định xem có nên đọc tiếp CV của bạn hay không.

  • Kỹ năng: Liệt kê những gì bạn biết và/hoặc có kinh nghiệm và nêu chính xác mức độ hiểu biết của bạn trong từng mục. Phần này cũng quan trọng không kém trong CV ngành công nghệ thông tin.
  • Kinh nghiệm làm việc: Tóm tắt, liệt kê những dự án, công ty mà bạn đã từng làm việc, kèm chức vụ và sắp xếp theo trình tự thời gian từ mới nhất đến cũ nhất. Đây có thể nói là phần quan trọng nhất của một CV IT. Lưu ý hãy trình bày kinh nghiệm làm việc theo thứ tự thời gian ưu tiên những vị trí gần đây nhất và những kinh nghiệm liên quan đến vị trí mà bạn đang ứng tuyển.
  • Thành tựu/ Dự án: Liệt kê vai trò của cá nhân trong các thành tựu/dự án đó. Thành tựu ở đây có thể là những gì bạn tự hào rằng mình đã làm được cho dự án, những điều bạn nhận ra và cải thiện được cho bản thân nhờ dự án.
  • Thông tin khác: Bằng cấp, giải thưởng, dự án riêng (nếu có)
  5 mẹo và mẫu CV IT để gây ấn tượng với nhà tuyển dụng!

Các mẫu CV trên TopDev

Mẫu tham khảo 1

Mẫu CV IT

Mẫu tham khảo 2

Mẫu CV IT

Mẫu tham khảo 3

cv lập trình viên

Tạo CV IT online, chuẩn ATS miễn phí trên TopDev

Có thể bạn quan tâm:

Xem thêm việc làm ngành it hàng đầu tại TopDev

Lập trình viên tại Việt Nam cần ít nhất 5 năm để hiểu rõ một công nghệ!

                                                                                                       TopDev via Noria

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

5 mẹo và mẫu CV IT để gây ấn tượng với nhà tuyển dụng!

CV của bạn chỉ có 5 giây để gây ấn tượng với nhà tuyển dụng!

“Rất nhiều CV của ứng viên ngành công nghệ thông tin đều viết quá dài và trình bày không được mạch lạc. Họ luôn cố gắng chia sẻ nhiều thông tin để có một hồ sơ thật ấn tượng. Tuy nhiên, trong một đợt tuyển dụng có rất nhiều CV được gửi về, nhà tuyển dụng chỉ có thời gian rất ngắn lướt qua và lựa chọn hồ sơ phù hợp để cân nhắc tiếp theo. Vì vậy để được nhận lời mời phỏng vấn, những gợi ý và mẫu CV IT dưới đây có thể giúp bạn nhanh chóng lọt vào mắt của nhà tuyển dụng” chia sẻ từ Mr. Xuân Sơn, Product Manager của Tuổi Trẻ Online.

1. ĐỘ DÀI

CV IT của bạn chỉ nên gói gọn trong 1 trang giấy khổ A4, nếu quá trình công tác của bạn lâu năm có thể kéo dài lên thành 2 trang để thể hiện rõ chi tiếp hơn.

2. CẤU TRÚC

Để thuận tiện cho nhà tuyển dụng, hãy đặt phần thông tin liên lạc ở phần đầu của hồ sơ, kế đến là tóm tắt về chuyên môn, những kỹ năng và kinh nghiệm của bạn, cuối cùng là phần học vấn và bằng cấp.  Hiện nay, bạn có thể tham khảo một số mẫu CV IT Tiếng anh và tiếng Việt phổ biến để tiết kiệm thời gian hơn cho việc thiết kế của bạn.

3. NỘI DUNG VÀ TỪ NGỮ

Phần tóm tắt chuyên môn hãy mô tả ngắn gọn về định hướng công việc và vị trí mong muốn sắp tới. Trong mục kỹ năng, chỉ liệt kê từ 4 đến 6 kỹ năng quan trọng nhất mang đến thành công trong công việc của bạn. Khi mô tả về kinh nghiệm làm việc, hãy sử dụng những động từ diễn tả thành công như “đạt được”, “gia tăng”… kèm theo một vài số liệu ấn tượng, điều này giúp bạn nổi bật hơn so với các CV khác.

Tạo CV IT online, chuẩn ATS miễn phí trên TopDev

4. ĐỊNH DẠNG

Hãy định dạng đơn giản và thông dụng nhất, để nhà tuyển dụng dễ dàng thấy được những điểm bạn muốn nhấn mạnh. Tránh dùng nhiều màu sắc, chữ in đậm hoặc viết hoa quá nhiều, chỉ gây khó chịu khi nhìn vào CV của bạn. Những thông tin quan trọng bạn nên đặt ở nữa trên hồ sơ và ở những khu vực nổi bật để gây chú ý nhà tuyển dụng khi họ lia mắt.

5. KIỂM TRA NHIỀU LẦN

Điều này giúp bạn tránh những sai sót lỗi chính tả, và đảm bảo bạn cung cấp đầy đủ thông tin để giới thiệu bản thân và thuyết phục nhà tuyển dụng hẹn bạn một buổi phỏng vấn. Đừng nghĩ tìm việc chỉ phụ thuộc vào may mắn. Sự chuẩn bị cẩn thận và đánh giá đúng khả năng của bản thân quyết định rất lớn đến việc thành công ứng tuyển của bạn. Bạn nên nhớ, nhà tuyển dụng chỉ có 5 giây để quyết định có xem tiếp hay không!

Đừng quên tham khảo nhiều mẫu CV đẹp dành cho lập trình viên, viết xong CV thì mạnh dạn apply vào những công việc IT với mức lương cực hấp dẫn ở TopDev nhé.

THÔNG BÁO THAM DỰ Sự kiện Thương mại điện tử LỚN NHẤT năm 2019!

Tháng 3 này, Hiệp Hội Thương mại điện tử Việt Nam – VECOM sẽ tổ chức sự kiện LỚN NHẤT mở đầu năm 2019:

DIỄN ĐÀN TOÀN CẢNH THƯƠNG MẠI ĐIỆN TỬ VIỆT NAM

Vietnam Online Bussiness Forum 2019 (VOBF 2019)

>>> Xem chi tiết: http://vobfhcm.vecom.vn/

Sự kiện với quy mô toàn quốc, quy tụ cộng đồng thương mại điện tử trong và ngoài nước.

Với sự góp mặt đến từ các đại diện: VECOM, NIELSEN, AMAZON, TIKI, SHOPEE và FACEBOOK, GOOGLE.

Chỉ diễn ra 1 lần duy nhất trong năm, đây là cơ hội lớn cho các doanh nghiệp tiến lên bước mở rộng quy mô với chủ đề “Scaling Up Your Bussiness – Vươn ra toàn cầu”.

Bạn sẽ được nghe và cũng tham gia trao đổi với các chuyên gia hỗ trợ mở rộng quy mô kinh doanh, giúp bạn tiếp cận khách hàng cả nước, hoặc khách hàng toàn cầu.

Nội dung chi tiết, bạn có thể tham khảo tại đây!

200 người đăng ký sớm nhất sẽ được ƯU ĐÃI mức vé EARLY BIRD – Giá thấp nhất

* Thời gian: Ngày 28 Tháng 03 năm 2019, 8.30AM – 5PM
* Địa điểm: Trung tâm Hội nghị Capella Parkview, Số 3 Đặng Văn Sâm, Phường 9, Phú Nhuận, Tp. Hồ Chí Minh

Lưu ý: Số lượng chỗ ngồi có hạn, BTC chỉ ưu tiên giữ chỗ cho những ai đăng ký và hoàn tất thanh toán sớm nhất!

Hotline: 0902940969 – Ms. Thạch

Stateless là gì? Stateful là gì?

stateful vs stateless là gì

Stateless

Trong lập trình web, chúng ta có sự tương tác giữa client với server. Phần mềm gồm 2 thành phần chính: phần mềm và data. Như vậy, một phần mềm được thiết kế theo tương tác client – server thì phần nhiều tập lệnh sẽ nằm phía server. Client có nhiệm vụ gửi dữ liệu lên để xử lý sau đó nhận kết quả trả về. Vậy stateful vs stateless là gì?

Để hiểu khái niệm stateful vs stateless là gì chúng ta cần phải biết rằng, Stateless là thiết kế không lưu dữ liệu của client trên server. Có nghĩa là sau khi client gửi dữ liệu lên server, server thực thi xong, trả kết quả thì “quan hệ” giữa client và server bị “cắt đứt” – server không lưu bất cứ dữ liệu gì của client. Như vậy, khái niệm “trạng thái” ở đây được hiểu là dữ liệu.

  Cách làm HTTPS hoạt động trên local trong 5 phút
  Responsive HTML bằng một dòng CSS

Stateful

Stateful là một thiết kế ngược lại, chúng ta cần server lưu dữ liệu của client, điều đó đồng nghĩa với việc ràng buộc giữa client và server vẫn được giữ sau mỗi request (yêu cầu) của client. Data được lưu lại phía server có thể làm input parameters cho lần kế tiếp.

Tổng kết và ví dụ

HTTP là một Application Protocol dạng stateless, tương tác client-server theo HTTP thì phần server sẽ không lưu lại dữ liệu của client. HTTP ban đầu chỉ được dùng đơn thuần cho website, client gửi request, server nhận request xử lý rồi trả về lại cho client hiển thị. Sau đó thì kết thúc 1 quy trình. Sau này người ta mới bắt đầu nâng cấp cho phép website giống như một ứng dụng stateful bao gồm html, database (mysql, mongodb…), transaction…

Có 4 cách lưu data của client khi xây dựng Web Application bao gồm: URL Rewriter, Form, Cookie, HTTP Session.

TopDev

Có thể bạn muốn đọc thêm:

Xem thêm IT Jobs for Developer hấp dẫn lương cao tại TopDev!

Kinh nghiệm xương máu sau 9 tháng làm Kỹ sư phần mềm (Phần 1)

kinh-nghiem-xuong-mau-sau-9-thang-lam-ky-su-phan-mem-phan-1

Tác giả: Benjamin Schachter

Sau 9 tháng làm việc tại Dexter với tư cách là một lập trình viên, tôi đã học được rất nhiều điều.  Cũng vì vậy, tôi đã quyết định viết một bài đăng blog chia sẽ về những trải nghiệm, cũng như một bài đăng một bài kĩ thuật về Self Positioning React Component mà tôi đã thực hiện trong vài tháng làm việc tại đây. Nhận được công việc mới chỉ là bước khởi đầu, làm thật tốt công việc được giao lại là một câu chuyện hoàn toàn khác, là một lập trình viên tôi hoàn toàn hiểu điều đó.

Có thể bạn muốn xem thêm:

  Cách tôi nhân rộng một dự án trị giá 86 triệu đô la chỉ với 57 dòng code!

  Đi phỏng vấn vị trí React Native cần trang bị những gì?

Những suy nghĩ của tôi về vai trò của mình đã thay đổi đáng kể kể từ khi tôi bắt đầu. Tôi đã nghĩ rằng trở thành một nhà phát triển phần mềm thì phải “xử lý” đám code càng nhanh càng tốt. Tuy nhiên, điều đó có vẻ không đúng thực tế lắm, như chuyện bạn viết code nhanh nhưng không chất lượng, buộc phải thay đổi liên tục sẽ không phải là cách giúp cho doanh nghiệp của bản có thể bị trì trệ hơn. May mắn thay, tôi đã gặp một người sếp có suy nghĩ tương tự, anh ấy cũng là dân phần mềm như tôi.

Mục tiêu của bạn sẽ là: Viết code chất lượng tốt và giao tiếp tốt với đồng nghiệp. Bạn không phải được trả tiền chỉ để code, bạn được trả tiền để suy nghĩ và tìm ra vấn đề. Sản phẩm đi kèm là tư duy tinh tể và chỉ dẫn máy làm theo dưới dạng code. Tôi muốn giải quyết vấn đề trong một dòng code dễ đọc hơn 10 dòng code khó hiểu. Tôi muốn giải quyết vấn đề trong 5 dòng code có thể đọc được so với một dòng code phức tạp, nested code kèm theo nhiều loại toán tử phức tạp. Tôi nghĩ bạn sẽ hiểu ý tôi muốn nói gì.

Muốn biết phải hỏi muốn giỏi phải học.

Những ngày đầu, tôi đã được sếp gửi cho một đường link để tham khảo, sau khi đọc tôi thật sự lo lắng về khả năng của mình. Tôi vẫn luôn rất ý thức trước khi đặt bất kỳ câu hỏi nào.

Tôi có hẳn riêng mình một check list trước khi nhờ người khác hỗ trợ điều gì:

  • Đây có phải là một câu hỏi mà tôi đã hỏi trước đó, và nếu có, tôi đã ghi lại câu trả lời ở đâu?
  • Đây có phải là điều tôi có thể Google không?
  • Điều này đã được ghi chép lại ở đâu đó trong nội bộ?
  • Chuyện gì đang xảy ra ở đây? Nguyên nhân sâu xa của lỗi hoặc các phản ứng bất ngờ mà tôi đang gặp phải là gì?
  • Tôi có thực sự hiểu câu hỏi tôi đang cố gắng để trả lời? Bạn có thể dành thời gian để đọc lại vấn đề một lần nữa thay vì đưa ra câu trả lời nửa vời hoặc câu trả lời hấp tấp.

Sau khi làm theo các bước này, tôi sẽ thử giải quyết vấn đề một mình, tìm một giải pháp đã được từng được document lại, hoặc hỏi một câu hỏi với ngữ cảnh tốt hơn và chi tiết hơn để giúp người khác dễ dàng trả lời hơn. Thậm chí, tốt hơn nữa, nếu tôi có thể đặt một câu hỏi hay và được trả lời qua đoạn chat, đồng đội của tôi không cần phải bỏ mọi thứ để giúp tôi.

Nếu tôi đã đi hết 90% con đường để giải quyết vấn đề thì thật ra chỉ cần 10% sự trợ giúp nữa thôi, một nhà phát triển cấp cao sẽ rất vui khi giúp bạn vì biết rằng bạn đã cố gắng hết mức có thể. Tìm kiếm người khác để giải quyết vấn đề của bạn không phải là một cách tuyệt vời để xây dựng lòng tin trong nhóm của bạn.

Những người thông minh thích những câu hỏi hay – vì vậy đừng ngại khi hỏi họ.

Video: Trọn bộ các chủ đề nâng cao của DevOps

Tránh những sai lầm cũ và hỏi những câu hỏi mà ai cũng đã biết câu trả lời.

Điều này nói dễ hơn là thực hiện, bạn có thể ứng dụng nó vào bất cứ ngành nào, không chỉ lập trình. Rất nhiều khái niệm và thông tin mới sẽ khiến mắc phải những sai lầm không đáng có, và là không thể tránh khỏi. Để hạn chế việc này, hãy nghiên cứu nhiều hơn, Google có đầy đủ thông tin. Xem kĩ các tài liệu, chúng là bạn của bạn. Hãy hỏi nếu như sau khi tìm hiểu bạn vẫn không có trả lời. Sau đó hãy viết chúng ra thành tài liệu, và đặt cho mình những mục tiêu để cải thiện nó.

Hãy đảm bảo rằng lần tiếp theo bạn gặp vấn đề tương tự, bạn biết phải làm gì. Tất cả chúng ta đều mắc sai lầm, nhưng việc tự ý thức và nỗ lực thay đổi là cách để mọi người trở nên tốt hơn.

Luôn xem lại những gì mình đã làm

Không ai thích đi qua PR và bảo bạn gỡ bỏ console.logs,  debuggers hoặc bảo bạn sửa lỗi linting. Tôi sẽ không xuất bản bài đăng này mà không đọc qua một vài lần và nhờ một người bạn xem qua nó trước.

Nhìn kĩ vào code của bạn và tự hỏi những câu hỏi sau:

  • Tôi đã viết một đoạn logic phức tạp. Có chức năng tương tự nào trong ứng dụng có thể giải quyết vấn đề này theo cách dễ đọc hơn, linh hoạt hơn hay chung chung không?
  • Nếu không, tôi có nhớ lý do tại sao tôi đã viết code này trong một tuần hay không? Nếu câu trả lời là không, tôi muốn đổi code hoặc bình luận nó. Người duyệt PR nên có một số lí do giải thích tại sao tôi lại đưa ra quyết định đó.
  • Hãy chắc chắn rằng code của bạn đang đi qua linting và được kiểm tra trước khi đưa nó cho bất kì ai khác.
  • Tôi có đang lặp lại hay không? Tôi có thể làm gọn logic bị lặp bằng một hàm hay không ?
  • Nếu đây là code của người khác mà tôi đang xem, tôi sẽ đưa ra những bình luận nào? Tôi muốn thay đổi gì để nó rõ ràng hơn?

Hãy thử nhìn vào code của bạn vào ngày hôm sau, bạn sẽ nhìn thấy rất nhiều sự khác biệt. Liệu có gì không ổn trong logic của các đoạn code? Liệu các component của bạn có xử lý logic đúng hay không?

Ngoài ra, việc tự đánh giá code tốt tiết kiệm thời gian và tiền bạc cho công ty. Đó là cách tốt nhất để bạn tìm ra bug của bạn và tự khắc phục chúng thay vì phải nhờ người khác tìm ra chúng sau hai ngày.

Điều cuối cùng nói về việc xem lại code của bạn. Test tất cả vào MỌI THỨ bạn đã làm. Tôi muốn code của tôi gửi cho bất cứ ai cũng phải tốt đến mức không thể chỉnh sửa. Nếu họ bấm vào một trang mới và thấy được một lỗi lớn hoặc màn hình trắng, điều đó chứng minh tôi đã không thực sự xem xét lại công việc của mình. Grep cho code bạn đã chỉnh sửa và đảm bảo rằng bạn không làm hỏng bất cứ thứ gì bằng việc bổ sung thêm vào component.

Nghe có vẻ ngớ ngẩn, nhưng những sản phẩm có quá nhiều dòng code lớn phức tạp và bạn có thể không nhận ra cho đến khi bạn đã làm hỏng một cái gì đó.

Tôi chắc rằng, bạn sẽ không muốn xem bản draft đầu tiên của bài blog này 🙂

Bài viết gốc được đăng tải tại Medium

Cách tôi nhân rộng một dự án trị giá 86 triệu đô la chỉ với 57 dòng code!

Tác giả: Tait Brown

Cảnh sát Victoria là cơ quan thực thi pháp luật chính của Victoria, Úc. Với hơn 16.000 xe bị mất cắp ở Victoria trong năm qua – tổn thất khoảng 170 triệu đô – cơ quan này đang thử nghiệm nhiều giải pháp kỹ thuật nhằm giải quyết nạn trộm xe. Họ gọi open source project – hệ thống này là BlueNet.

Để giúp ngăn chặn việc bán xe ăn cắp, đã có một dịch vụ dựa trên web tên VicRoads để kiểm tra tình trạng đăng ký xe. Bộ cũng đã đầu tư vào một máy quét tấm giấy cố định – một camera cố định quét qua lưu lượng để tự động xác định các xe bị đánh cắp.

Đừng hỏi tôi tại sao, nhưng một buổi chiều tôi đã có mong muốn mẫu thử nghiệm một chiếc máy quét nhãn đĩa xe gắn máy sẽ tự động thông báo cho bạn nếu một chiếc xe đã bị đánh cắp hoặc đã không đăng ký. Hiểu rằng những thành phần cá nhân này tồn tại, tôi tự hỏi làm thế nào để kết hợp chúng với nhau khó khăn đến thế nào.

Cảnh sát Victoria vừa mới tung ra bản thử nghiệm về một phần mềm tương tự, và chi phí phát hành ước tính đã ở đâu đó trong khoảng 86.000.000 đô. Một người bình luận đã chỉ ra rằng với 86 triệu đô để trang bị cho 220 chiếc xe thì sẽ tính ra trung bình 390,909 đô mỗi chiếc.

Chắc chắn chúng ta có thể làm tốt hơn một chút.

Các tiêu chí làm nên thành công

Trước khi bắt đầu, tôi lên kế hoạch về yêu cầu chính cho thiết kế sản phẩm:

#1: Việc xử lý hình ảnh phải được thực hiện local

Streaming video trực tiếp đến nơi xử lí trung tâm dường như là cách tiếp cận hiệu quả nhất để giải quyết vấn đề này. Bên cạnh tuyệt vời cho lưu lượng truy cập dữ liệu, bạn cũng giới thiệu độ trễ mạng vào một quá trình có thể đã được khá chậm.

Mặc dù một thuật toán về centralized machine learning chỉ có thể có được sự chính xác hơn theo thời gian, chúng ta muốn tìm hiểu nếu một local thực hiện trên thiết bị sẽ là “đủ tốt”.

#2: Phải làm việc với hình ảnh có chất lượng thấp

Bởi vì tôi không có Raspberry Pi hoặc webcam USB nên tôi sẽ sử dụng footage của dashcam – nó có sẵn và là nguồn lý tưởng cho dữ liệu mẫu. Giống như added bonus, dashcam video đại diện cho chất lượng chung của footage mà bạn mong muốn từ các camera gắn trên xe.

#3: Cần được xây dựng dựa trên công nghệ mã nguồn mở 

Dựa trên một phần mềm độc quyền có nghĩa là bạn sẽ get stung mỗi khi bạn yêu cầu thay đổi hoặc tăng cường – và tiếp tục stinging cho mỗi yêu cầu được thực hiện sau đó. Sử dụng công nghệ mã nguồn mở là không cần suy nghĩ.

Relying upon a proprietary software means you’ll get stung every time you request a change or enhancement — and the stinging will continue for every request made thereafter. Using open source technology is a no-brainer.

Kết luận

Ở level cao, giải pháp của tôi lấy một hình ảnh từ dashcam video, đưa nó qua qua một hệ thống nhận dạng tấm giấy phép mã nguồn mở được cài đặt cục bộ trên thiết bị, truy vấn dịch vụ kiểm tra đăng ký, và sau đó trả về kết quả.

Dữ liệu được trả lại cho thiết bị được cài đặt trong xe thực thi pháp luật bao gồm xe và mô hình (mà nó chỉ sử dụng để xác minh các xe đã bị đánh cắp), tình trạng đăng ký và bất kỳ thông báo nào của chiếc xe được báo cáo bị đánh cắp.

Nghe có vẻ đơn giản, đó là bởi vì nó thực sự là. Ví dụ, xử lý hình ảnh tất cả có thể được xử lý bởi thư viện openalpr.

Tất cả được dùng để nhận diện các ký tự trên license plate:

openalpr.IdentifyLicense(imagePath, function (error, output) {
   // handle result
});

Đây là những điều của tôi chứng minh khái niệm như sau:

// Open form and submit enquire for `rego`
function getInfo(rego) {
	horseman
	  .userAgent('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0')
	  .open(url)
	  .type('#registration-number-ctrl input[type=text]', rego)
	  .click('.btn-holder input')
	  .waitForSelector('.ctrl-holder.ctrl-readonly')
	  .html()
	  .then(function(body) {
	  	console.log(processInfo(body, rego));
	    return horseman.close();
	  });
}

// Scrape the results for key info
function processInfo(html, rego) {
	var $ = cheerio.load(html);
	var vehicle = $('label.label').filter(function() {
	  return $(this).text().trim() === 'Vehicle:';
	}).next().text().trim();

	var stolen = $('label.label').filter(function() {
	  return $(this).text().trim() === 'Stolen status:';
	}).next().text().trim();

	var registration = $('label.label').filter(function() {
	  return $(this).text().trim() === 'Registration status & expiry date:';
	}).next().text().trim();

	return {
		rego,
		vehicle,
		stolen,
		registration
	};
}

Kết quả

Sự công nhận về giấy phép mã nguồn mở được không được công nhận. Ngoài ra, các thuật toán nhận dạng hình ảnh có thể không được tối ưu hóa cho tấm giấy phép của Úc.

Giải pháp đã có thể nhận diện tấm giấy phép trong một lĩnh vực rộng.

Các chú thích được thêm vào có hiệu lực. Biển số được nhận dạng mặc dù reflection và lens distortion.

Mặc dù, giải pháp này đôi khi gặp vấn đề với các chữ cái trên biển số.

Sai lầm trên biển số, nhầm M thành H

Nhưng … các giải pháp cuối cùng sẽ có được sự chính xác.

Một vài khung hình sau đó, M được xác định chính xác và ở mức độ tin cậy cao hơn

Như bạn thấy trong hai hình ảnh trên, việc xử lý hình ảnh một vài khung hình sau đó đã nhảy lên từ mức độ tự tin là 87% đối với tóc trên 91%.

Với tính chính xác có thể được cải thiện bằng cách tăng tỷ lệ mẫu và sau đó sắp xếp theo thứ hạng độ tin cậy cao nhất. Ngoài ra, một ngưỡng có thể được đặt chỉ chấp nhận sự tự tin lớn hơn 90% trước khi tiếp tục xác nhận số đăng ký.

Đây là các bản sửa lỗi code đầu tiên và không loại trừ việc đào tạo phần mềm nhận dạng license plate với bộ dữ liệu cục bộ.

Còn về 86,000,000 đô

Để công bằng, tôi hoàn toàn không biết có bao nhiêu con số 86 triệu đô – và tôi cũng không thể nói về tính chính xác của công cụ mã nguồn mở của tôi mà không cần đào tạo so với hệ thống BlueNet.

Tôi hy vọng phần của ngân sách đó bao gồm việc thay thế một số cơ sở dữ liệu và các ứng dụng phần mềm để hỗ trợ truy vấn các cấp phép có tần số cao, độ trễ thấp trên mỗi xe.

Mặt khác, chi phí khoảng 391k đô mỗi xe dường như khá tốn kém – đặc biệt nếu BlueNet không chính xác và không có các dự án công nghệ thông tin quy mô lớn để ngừng hoạt động hoặc nâng cấp các hệ thống bị phụ thuộc.

Các ứng dụng tương lai

Mặc dù rất dễ bị cuốn vào bản chất Orwellian của một mạng lưới những người tán thành tấm giấy phép luôn có nhiều ứng dụng tích cực của công nghệ này. Hãy tưởng tượng một hệ thống thụ động quét người cùng đi xe và tự động cảnh báo các nhà chức trách và thành viên gia đình đến vị trí hiện tại của họ.

Xe của Teslas đã được làm đầy bằng camera và cảm biến với khả năng nhận được các bản cập nhật OTA – hãy tưởng tượng biến những chiếc xe này thành một đội quân tốt. Các tài xế của Ubers và Lyft cũng có thể được trang bị các thiết bị này để tăng đáng kể phạm vi bảo hiểm.

Sử dụng công nghệ mã nguồn mở và các component hiện có, có vẻ như có thể cung cấp một giải pháp cung cấp một tỷ lệ lợi nhuận cao hơn nhiều – cho một khoản đầu tư ít hơn 86 triệu đô.

Có thể bạn muốn xem:

  Top 5 công cụ mã nguồn mở dành cho MySQL administrator
  Những lập trình viên phiên bản X-men: Những code project "dị" nhất trên GitHub

Bài viết gốc được đăng tải tại Freecodecamp

AI đi vào thực tiễn (Phần 2): Trí tuệ nhân tạo đang xâm nhập và định hình tương lai

Ngày nay, chúng ta đã quen thuộc với các khái niệm như Trí tuệ nhân tạo (artificial intelligence). Điển hình như các gợi ý, đề xuất sách và phim trên Amazon và Netflix hay danh sách phát nhạc trên Spotify, Youtube, … Nhưng năm 2019 sẽ mang lại cho chúng ta những gì có thể xem là “siêu cá nhân hóa” (hyper-personalization) cho người tiêu dùng thông qua một loạt các ứng dụng về sức khỏe, tài chính, mua sắm và mọi thứ liên quan. Điều này là một bước tiến mới của các ứng dụng AI khi khả năng dự đoán chính xác ngày càng tăng cao, ít tốn chi phí hơn cho dù dữ liệu cá nhân người dùng thì ngày càng dày đặc. Dưới đây là một số lĩnh vực mà chúng ta có thể mong đợi “siêu cá nhân hóa” dựa trên AI vào năm nay.

1. Thể dục thể chất (Physical Fitness)

Sức khỏe là một trong những lĩnh vực hot của các ứng dụng AI. Các ứng dụng thể dục và thiết bị đeo như Fitbit và Apple Watch và thậm chí một số tai nghe VR có thể tự động thu thập dữ liệu của người dùng – chẳng hạn như bạn đã đi bao nhiêu bước hay leo bao nhiêu nấc thang – và tổng hợp với tất cả các dữ liệu của toàn bộ người dùng để tạo ra hồ sơ thể hình riêng biệt dành cho từng người và lên các kế hoạch theo đó, giống như bộ điều chỉnh nhiệt thông minh Nest sử dụng dữ liệu từ khắp các gia đình để tối ưu hóa các kế hoạch sử dụng năng lượng. Những gợi ý như “Giảm lượng thức ăn của bạn 210 calo / ngày để tăng tuổi thọ thêm 1,5 năm” sẽ không còn xa.

Physical Fitness

Bên cạnh đó, vấn đề thường xuyên ở hầu hết các ứng dụng tập thể dục – sức khỏe – thể hình là việc người dùng mệt mỏi khi nhập các loại thực phẩm họ đã ăn – hoặc gian lận và chọn không nhập chúng. Điều đó dẫn đến nếu không có dữ liệu chính xác thì các phân tích là vô nghĩa. Tuy nhiên, AI đã có giải pháp khắc phục: Khi AI trở nên tiên tiến hơn, bạn chỉ cần chụp ảnh những gì bạn ăn hoặc ứng dụng sẽ theo dõi lượng thức ăn thông qua camera của smartphone và tự động hóa quy trình thu thập dữ liệu. Một số ứng dụng như Lose It đã sử dụng công nghệ này.

2. Sức khỏe tâm thần (Mental Heath)

*Là một trạng thái không chỉ không có rối loạn hay dị tật tâm thần mà còn là một trạng thái tâm thần hoàn toàn thoải mái, cần phải có chất lượng nuôi sống tốt, có được sự cân bằng và hòa hợp giữa cá nhân, người xung quanh và môi trường xã hội.

Bạn có muốn biết thời gian nào trong ngày là lúc tâm trạng bạn suy sụp nhất? Hay cách mà mọi người tác động đến bạn sẽ có phản ứng với mức năng lượng như thế nào? Sẽ sớm có một ứng dụng cho điều đó. Các ứng dụng có thể tự thực hiện cảm biến, sử dụng cảm biến sinh học để theo dõi nhịp tim / nhịp thở, phản ứng ngoài da và các chỉ số khác để đánh giá phản ứng của chúng ta đối với mọi thứ từ buổi sáng đi làm đến bữa tối với luật pháp. Sau đó, chúng ta có thể sử dụng các báo cáo toàn diện được tạo ra cho lối sống thoải mái hơn, ít căng thẳng hơn, hoặc ít nhất là nhận thức được con người và các tình huống có nhiều khả năng làm cho lòng bàn tay đổ mồ hôi.

Mental Heath

Công nghệ nhận dạng khuôn mặt dựa trên AI cũng có ứng dụng rộng rãi trong sức khỏe tâm thần. Affectiva, một doanh nghiệp xuất phát từ MIT, cung cấp các sản phẩm ứng dụng “kích hoạt cảm xúc” bằng cách đọc và phân tích biểu cảm khuôn mặt với độ chính xác tốt hơn con người. Vì vậy, các ứng dụng sẽ đánh giá mức độ căng thẳng hoặc các triệu chứng lo âu / trầm cảm của bạn và đưa ra gợi ý phù hợp: “Thực hiện hai phút thở sâu” hoặc “Hẹn gặp bác sĩ trị liệu”. Công ty cũng đã phát triển một sản phẩm trong xe hơi để đánh giá trạng thái cảm xúc trong khi lái xe từ biểu cảm khuôn mặt và giọng nói, cải thiện an toàn khi trên đường.

  AI đi vào thực tiễn (Phần 1): Những biến động của năm 2018

3. Tài chính cá nhân (Personal Finance)

Có rất nhiều câu hỏi đặt ra trong lĩnh vực này: Làm thế nào để chúng ta biết những khoản đầu tư nào phù hợp nhất với sự tăng trưởng, rủi ro và nhu cầu thanh khoản của từng cá nhân? Và khi nào nên đầu tư vào chúng? Và đầu tư bao nhiêu? Và khi nào bán?

Câu trả lời ngắn gọn: “Chúng tôi không rõ. Nhưng AI thì có thể”

Các ứng dụng AI sẽ ngày càng cải thiện khả năng dự đoán rủi ro của chúng ta và các tính năng của hồ sơ đầu tư khác bằng cách “đọc giữa các dòng” theo nghĩa đen. Các nhà nghiên cứu đã phát triển một thuật toán deep learning có thể phân tích lời nói hoặc văn bản để đánh giá mức độ tin cậy của người nói / người viết về bất kỳ điều gì về hiệu quả của sản phẩm nào đó và xác định tính chính xác của các dự báo thu nhập được.

Personal Finance

Một ứng dụng tài chính cá nhân kết hợp công nghệ như vậy có thể yêu cầu bạn viết phản hồi cho một số tình huống chấp nhận rủi ro khác nhau, sau đó đánh giá mức độ tự tin của bạn về mỗi kế hoạch để tạo ra một kế hoạch đầu tư được cá nhân hóa cao trong ngắn hạn hoặc dài hạn. Trên thực tế, công ty Narrative Science ở giai đoạn đầu đang sử dụng xử lý ngôn ngữ tự nhiên để phát triển các ứng dụng loại này và có khả năng giúp người dùng hiểu được mức độ chịu đựng của họ đối với các lớp đầu tư như cổ phiếu hòa vốn.

4. Mua sắm (Shopping)

Là người mua sắm, chúng ta muốn được cá nhân hóa trong các lựa chọn của mình, từ phụ kiện, quần áo cho đến ô tô; nhưng bên cạnh đó tôi cũng muốn mua những thứ mà những người giống tôi mua hoặc những thứ tôi muốn giống như vậy. Các ứng dụng AI tập trung vào bán lẻ có thể giúp xác định sở thích sản phẩm của chúng ta bằng cách không chỉ nhìn vào các hành vi mua hàng trong quá khứ, mà cả những người giống chúng ta, để đưa ra các đề xuất dựa trên hình thức đám đông hậu trường (behind-the-scenes crowdsourcing). Và điều này đã xảy ra khi Amazon đã tổng hợp dữ liệu cá nhân để phát triển các đề xuất sản phẩm và các dự đoán khác trong một thời gian trước đó. Nhưng cụ thể nó sẽ xuất hiện vào năm 2019 – trên các ngành và loại sản phẩm – và mức độ cá nhân hóa để mong đợi sẽ tăng lên.

Shopping

Nhận dạng khuôn mặt nâng cao đồng nghĩa với việc trải nghiệm tại cửa hàng được cá nhân hóa hơn nhiều. Ví dụ, các nhà bán lẻ sẽ có thể nhận ra các khách hàng của chương trình khách hàng thân thiết đã bước vào và đẩy các giao dịch, các đề xuất được cá nhân hóa cho họ thông qua điện thoại của họ, cùng với việc cung cấp dịch vụ ở mức cao hơn (như tư vấn mua sắm). Nhận dạng khuôn mặt cũng sẽ cho phép doanh nghiệp phân tích phong cách mua sắm của khách hàng hoặc phân khúc hoặc đường dẫn trong cửa hàng chặt chẽ hơn.

Nhìn chung, siêu cá nhân hóa dựa trên AI mang lại lợi ích tiềm năng rất lớn nhưng nó cũng kèm theo một lời cảnh báo: Các doanh nghiệp càng sở hữu nhiều dữ liệu cá nhân của chúng ta, nguy cơ lạm dụng và mất cắp dữ liệu càng tiềm ẩn. Tuy nhiên, các vấn đề bảo mật đang được đẩy mạnh cùng với AI để nhằm đảm bảo độ an toàn và sự trải nghiệm để giúp cuộc sống chúng ta ngày một tốt hơn. Hiện nay TopDev đang có sự kiện “AI và NLP – Ứng dụng thực tiễn và cách bắt đầu” được mở rộng hơn từ chuỗi sự kiện “AI – Ứng dụng thực tiễn và cách bắt đầu” với nhiều cái nhìn đa chiều về AI và NLP trong thực tế xung quanh chúng ta.

AI&NLP

THÔNG TIN CHI TIẾT

Thời gian & địa điểm: 17:30 – 21:00 ngày 19/03/2019

Link đăng ký: https://meetup.vn/e/EL2?src=a

ĐỘC GIẢ TOPDEV DÙNG NGAY CODE MARCH2019 ĐỂ NHẬN ƯU ĐÃI 50.000Đ CHO SỰ KIỆN TRÊN.

AI đi vào thực tiễn (Phần 1): Những biến động của năm 2018

Năm 2018 có thể coi là một dấu mốc lớn trong lĩnh vực trí tuệ nhân tạo (AI), dù có cả những nốt thăng và trầm.

Các tin đáng chú ý về AI trong năm 2018

Xe tự động: Bất kể những diễn biến không mấy lạc quan, rõ ràng năm 2018 là một năm quan trọng trong việc thúc đẩy ngành công nghiệp sản xuất xe tự vận hành đi lên. Hàng loạt cuộc thử nghiệm được tiến hành của Uber, Waymo (của Google), Lyft cùng nhiều công ty khác được diễn ra và đã có những thành quả đầu tiên sau giai đoạn kiện tụng về quyền sở hữu trí tuệ giữa các ông lớn. Có thể nói xe tự lái đã chính thức có mặt dù vẫn còn nhiều vấn đề phải giải quyết trước khi các phương tiện có khả năng tự chủ hoàn toàn.

Xe tự động

Trợ lý ảo, ứng dụng tại các doanh nghiệp: Các thiết bị tích hợp trợ lý “ảo” và được điều hành thông qua giọng nói đã được đầu tư về hình ảnh, tính năng một cách đáng kể và có một vị trí lớn trong xu hướng AI trong năm 2018. Các trợ lý ảo có thể gọi điện, trò chuyện với người thật hoàn hảo tới mức không thể hình dung là họ đang nói chuyện với máy. Chatbot trở nên phổ biến tại lĩnh vực dịch vụ chăm sóc khách hàng, trong khi những công nghệ tự quản lý thông tin tích hợp AI đã dần hoàn thiện hơn. Nhiều công ty cũng đang nỗ lực đưa ra những bước tiến mới trong quá trình xử lý ngôn ngữ tự nhiên, giúp doanh nghiệp có thể tìm hiểu rõ hơn các luồng dữ liệu phi cấu trúc của họ. Hoạt động marketing có sử dụng hỗ trợ từ AI cũng là một trong những xu hướng nổi bật trong năm 2018, với khái niệm siêu cá nhân hóa được chú ý hơn khi các công ty nhận ra lợi ích của AI trong hoạt động này.

 

trợ lý ảo

Chương trình AI cấp quốc gia: Rất nhiều nước trên thế giới đều đẩy mạnh và xem việc phát triển AI là một trong những nhiệm vụ hàng đầu. Điển hình như lộ trình ba bước của Trung Quốc với nguồn quỹ đầu tư 5 tỷ USD cùng một công viên công nghệ trị giá 2,1 tỷ USD để tạo điều kiện phát triển công nghệ, Mỹ và Pháp với kế hoạch đầu tư lần lượt là 2 tỷ USD và 1,5 tỷ Euro cho các sáng kiến liên quan đến AI, …

Chương trình AI cấp quốc gia

Riêng ở Việt Nam, “Kế hoạch phát triển trí tuệ nhân tạo ở Việt Nam đến năm 2025” đã được ban hành và sẽ tập trung phát triển các công nghệ nền tảng về xử lý và nhận dạng âm thanh, tiếng nói, hình ảnh, thị giác máy, tích hợp dữ liệu, bản đồ số, quản lý dây chuyền sản xuất. Nhiều trường đại học cũng đã bổ sung và cập nhật các kiến thức liên quan đến AI vào chương trình đào tạo cũng như một số trường đào tạo miễn phí ngành Robot và Trí tuệ nhân tạo cho sinh viên.

Bên cạnh đó, các mảng khác của trí tuệ nhân tạo cũng thu được những kết quả rất tích cực: nhận diện khuôn mặt ngày càng phát triển giúp tăng cường khả năng bảo mật và quản lý, quá trình tự động hóa robot (RPA) và học máy ngày càng được đẩy mạnh, các nguồn vốn được rót vào cũng như bộ luật AI được ban hành giúp cho AI ngày càng lan rộng và phát triển mạnh mẽ hơn.

AI một trong những công nghệ đang xâm nhập vào thực tiễn, góp phần định hướng tương lai đang tạo ra nhiều cơ hội cho các doanh nghiệp trong thời kỳ chuyển đổi số. Nhưng để đi đến những điều lớn lao, AI là gì và đang được ứng dụng xung quanh chúng ta như thế nào? bạn nên nắm rõ. Một trong những tech-meetup đáng chú ý vào tháng 03/2019 là “AI và NLP – Ứng dụng thực tiễn và cách bắt đầu”

Độc giả TopDev Blog có thể tham gia sự kiện theo thông tin bên dưới:

AI&NLP

Persol – Quy mô tầm cỡ và cơ hội rộng mở chào đón Technical Project Manager

Persol - Quy mô tầm cỡ và cơ hội rộng mở chào đón Technical Project Manager

Với sự lớn mạnh hơn 90 công ty trong và ngoài nước, Persol vẫn đang tiếp tục phát triển thêm nguồn lực để mở rộng quy mô. Với Persol, con người chính là nền tảng, mỗi cá nhân cần được đầu tư cho sự phát triển. Đó cũng là lý do tại sao môi trường việc làm tại Persol lại có thể thu hút được nhiều nhân tài.

Persol: “Work and Smile”!

Thương hiệu Persol có từ tháng 7 năm 2016, đến nay tập đoàn Persol đã lớn mạnh với hơn 90 công ty trong và ngoài nước, tham gia vào nhiều lĩnh vực: cung cấp các giải pháp về System, Outsourcing, Product, Global team system và Tư vấn công nghệ. Là một đối tác phát triển cá nhân, Tập đoàn Persol hoạt động để rút ra tiềm năng cá nhân và tối đa hóa các cơ hội sống. Persol mong muốn giúp tạo ra một xã hội trong đó tất cả những người làm việc có thể trải nghiệm niềm vui của Tagline nhóm của Persol: “Work and Smile”.

Song song với việc phát triển quy mô, Persol luôn luôn chú trọng đến phát triển nhân lực và thu hút nguồn lao động chất lượng. Bởi vì, Persol bắt nguồn từ “Person- con người” với tâm niệm: sự phát triển của mỗi cá nhân chính là cốt lõi sự phát triển của công ty.

5 yếu tố tạo nên giá trị của Persol Process & Technology Vietnam:

➤ Your job – your joy: niềm vui đến từ chính công việc bạn đang làm

➤ Work & smile: hăng say lao động và thư giãn khi cần

➤ When you like your work everyday is a holiday: được làm điều mình thích mỗi ngày, đó là sự tận hưởng.

➤ Stay hungry- Stay foolish: hãy cứ khao khát, hãy cứ dại khờ

➤ Hiring the right person: tuyển đúng người, dùng đúng lúc.

Persol không ngừng chiêu mộ nhân tài với nhiều đãi ngộ xứng đáng cả vật chất lẫn tinh thần. Đặc biệt hiện tại, Persol đang mở ra cơ hội cho những tài năng muốn thử sức ở vai trò lãnh đạo – vị trí Technical Project Manager (PHP, Java, C#) với mức lương lên đến 3,000 USD.

Persol Việt Nam tìm bạn đồng hành, vạch lối đi mới cho nền công nghệ Việt Nam

Persol luôn mong mỏi kết nối được với những nhà quản lý tài năng, cùng theo đuổi đam mê, cùng gặt hái cái kết ngọt cho những người trẻ đầy ý chí. Technical Project Manager (PHP, Java, C#) chính là tấm vé mà Persol muốn gửi đến các kỹ sư công nghệ tại Việt Nam với những quyền lợi đặc biệt:

  • Mức lương hậu hĩnh lên tới $3,000;
  • Được review hiệu quả công việc nâng mức thu nhập 2 lần/ năm;
  • Nghỉ phép thả ga tận 24 ngày lương vẫn về đều đặn;
  • Luôn có cà phê, trà và bia miễn phí giải tỏa “cơn say” công việc;
  • Có đãi ngộ riêng dành cho nhân viên có trẻ nhỏ;
  • Được chi thoải mái trong những ngày đặc biệt của nhân viên và gia đình;Vi vu du ngoạn với company trip, team building hàng tháng;
  • Được làm việc với đối tác người Nhật, rèn luyện kỹ năng chuyên môn trong môi trường toàn cầu;
  • Nhận sự hỗ trợ nhiệt tình từ đồng nghiệp, kề vai sát cánh chinh chiến các dự án lớn nhỏ.

Tìm việc làm Project Manager tại các doanh nghiệp hàng đầu trên TopDev

Mô tả công việc:

  • Làm việc trực tiếp với khách hàng Nhật Bản;
  • Thành thạo tiếng Nhật để hiểu rõ những yêu cầu từ phía khách hàng;
  • Quản lý thay đổi phạm vi dự án, tiến độ dự án;
  • Quản lý lịch trình để đảm bảo công việc được xác định, phân công và hoàn thành đúng hạn;
  • Phân tích, làm rõ và chuyển giao yêu cầu cho các thành viên;
  • Tham gia vào việc design documents, test cases.

Yêu cầu:

  • Bằng N2 tiếng Nhật;
  • Kỹ năng viết và giao tiếp Tiếng Anh tốt;
  • Ít nhất 4 năm kinh nghiệm trong lĩnh vực phát triển phần mềm;
  • Có kiến thức vững chắc về JAVA hoặc PHP hoặc C;
  • Ưu tiên ứng viên đã làm việc tại Nhật Bản hoặc có kinh nghiệm làm việc với các đối tác Nhật Bản.

Để có thể cùng nhau đi trên chặng đường dài phía trước, Pesol rất mong tìm được người phù hợp để tiếp tục đồng hành chinh phục những giấc mơ lớn. Với vị trí Technical Project Manager (PHP, Java, C#) tại Persol, con số $3,000 không chỉ là mức thu nhập xứng đáng mà còn là niềm tự hào cho sự nỗ lực và ý chí của chính bạn!

Xem ngay những tin đăng tuyển dụng IT mới nhất trên TopDev

Xác thực và phân quyền trong Microservices

Tác giả: Hòa Nguyễn

Xác thực (authentication, trả lời câu hỏi bạn là ai) và phân quyền (authorization, trả lời câu hỏi bạn có thể làm được gì) microservices luôn là thành phần không thể thiếu của mọi hệ thống, nhưng mức độ áp dụng thì lại tùy thuộc vào từng giai đoạn.

Nếu bạn làm mọi thứ chặt chẽ ngay từ đầu, nó có thể làm tăng độ phức tạp và làm chậm sự phát triển của công ty. Nhưng nếu bạn làm nó quá muộn, thì có thể bạn sẽ hứng chịu nguy cơ bị tấn công và rủi ro từ đó. Với 1 công ty e-commerce như Tiki, rủi ro đó rất hiện hữu với các hệ thống liên quan tới thanh toán, tiền ảo (Tiki Xu), mã khuyến mại (coupon), phiếu quà tặng (giftcard) và nhiều hệ thống nhạy cảm khác…

Bắt đầu từ Monolithic

Tiki xuất phát là 1 hệ thống monolithic, thông thường ở hệ thống như vậy sẽ có 1 module chung quản lý việc xác thực và phân quyền, mỗi user sau khi đăng nhập sẽ được cấp cho 1 Session ID duy nhất để định danh.

Phía client có thể lưu Session ID lại dưới dạng cookie và gửi kèm nó trong mọi request. Hệ thống sau đó sẽ dùng Session ID được gửi đi để xác định danh tính của user truy cập, để người dùng không cần phải nhập lại thông tin đăng nhập lần sau

Khi Session ID được gửi lên, server sẽ xác định được danh tính của người dùng gắn với Session ID đó, đồng thời sẽ kiểm tra quyền của user xem có được truy cập tác vụ đó hay không.

Giải pháp session và cookie vẫn có thể sử dụng, tuy nhiên ngày nay chúng ta có nhiều yêu cầu hơn, chẳng hạn như các ứng dụng Hybrid hoặc SPA (Single Page Application) có thể cần truy cập tới nhiều hệ thống backend khác nhau, vì vậy session và cookie lấy từ 1 server có thể không sử dụng được ở server khác.

Bài toán khó Microservices

Trong kiến trúc microservices, hệ thống được chia nhỏ thành nhiều hệ thống con, đảm nhận các nghiệp vụ và chức năng khác nhau. Mỗi hệ thống con đó cũng cần được xác thực và phân quyền, nếu xử lý theo cách của kiến trúc Monolithic ở trên chúng ta sẽ gặp các vấn đề sau:

  • Mỗi service có nhu cầu cần phải tự thực hiện việc xác thực và phân quyền ở service của mình. Mặc dù chúng ta có thể sử dụng các thư viện giống nhau ở mỗi service để làm việc đó tuy nhiên chi phí để bảo trì thư viện chung đó với nhiều nền tảng ngôn ngữ khác nhau là quá lớn.
  • Mỗi service nên tập trung vào xây dựng các nghiệp vụ của mình, việc xây dựng thêm logic về phân quyền làm giảm tốc độ phát triển và tăng độ phức tạp của các service.
  • Các service thông thường sẽ cung cấp các interface dưới dạng RESTful API, sử dụng protocol HTTP. Các HTTP request sẽ được đi qua nhiều thành phần của hệ thống. Cách truyền thống sử dụng session ở server (stateful) sẽ gây khó khăn cho việc mở rộng hệ thống theo chiều ngang.
  • Service sẽ được truy cập từ nhiều ứng dụng và đối tượng sử dụng khác nhau, có thể là người dùng, 1 thiết bị phần cứng, 3rd-party, crontab hay 1 service khác. Việc xác định định danh (identity) và phân quyền (authorization) ở nhiều ngữ cảnh (context) khác nhau như vậy là vô cùng phức tạp

Dưới đây là một số giải pháp, kỹ thuật và hướng tiếp cận mà Tiki đã áp dụng cho bài toán này.

Định danh

Sử dụng JWT

JWT (Json Web Token) là 1 loại token sử dụng chuẩn mở dùng để trao đổi thông tin kèm theo các HTTP request. Thông tin này được xác thực và đánh dấu 1 cách tin cậy dựa vào chữ ký. JWT có rất nhiều ưu điểm so với session.

  • Stateless, thông tin không được lưu trữ trên server.
  • Dễ dạng phát triển, mở rộng.
  • Performance tốt hơn do server đọc thông tin ngay trong request (nếu session thì cần đọc ở storage hoặc database)

Mã hóa RSA cho JWT

Phần chữ ký sẽ được mã hóa lại bằng HMAC hoặc RSA.

  • HMAC: đối tượng khởi tạo JWT (token issuer) và đầu nhận JWT (token verifier) sử dụng chung 1 mã bí mật để mã hóa và kiểm tra.
  • RSA: sử dụng 1 cặp key, đối tượng khởi tạo JWT sử dụng Private Key để mã hóa, đầu nhận JWT sử dụng Public Key để kiểm tra.

Như vậy với HMAC, cả 2 phía đều phải chia sẻ mã bí mật cho nhau, và đầu nhận JWT hoàn toàn có thể khởi tạo 1 mã JWT khác hợp lệ dựa trên mã bí mật đó. Còn với RSA, đầu nhận sử dụng Public Key để kiểm tra nhưng không thể khởi tạo được 1 JWT mới dựa trên key đó. Vì vậy mã hóa sử dụng RSA giúp cho việc bảo mật chữ ký tốt hơn khi cần chia sẻ JWT với nhiều đối tượng khác nhau.

Sử dụng Opaque Token khi muốn để kiểm soát phiên làm việc tốt hơn

Opaque Token (còn được gọi là stateful token) là dạng token không chứa thông tin trong nó, thông thường là 1 chuỗi ngẫu nhiên và yêu cầu 1 service trung gian để kiểm tra và lấy thông tin. Ví dụ:

{
"access_token": "c2hr8Jgp5jBn-TY7E14HRuO37hEK1o_IOfDzbnZEO-o.zwh2f8SPiLKbcMbrD_DSgOTd3FIfQ8ch2bYSFi8NwbY",
"expires_in": 3599,
"token_type": "bearer"
}

Transparent Token (còn được gọi là stateless token) thông thường chính là dạng JWT, token này bản thân chứa thông tin và không cần 1 service trung gian để kiểm tra. Hãy cùng so sánh 2 loại token này

Như vậy ta có thể thấy Transparent Token mang lại tốc độ tốt hơn, đơn giản dễ sử dụng với cả 2 phía, không phù thuộc vào 1 server trung tâm để kiểm tra. Còn Opaque Token kiểm soát tốt hơn các phiên làm việc của đối tượng, chẳng hạn khi bạn muốn thoát tất cả các thiết bị đang đăng nhập.

OAuth 2

Các token sẽ được khởi tạo thông qua OAuth 2, là phương thức chức thực phổ biến nhất hiện nay, mà qua đó một service, hay một ứng dụng bên thứ 3 có thể đại điện (delegation) cho người dùng truy cập vào 1 tài nguyên của người dùng nằm trên 1 dịch vụ nào đó.

OAuth 2 là chuẩn mở, có đầy đủ tài liệu, thư viện ở tất cả các ngôn ngữ khác nhau giúp cho việc tích hợp, phát triển dựa trên nó trở nên dễ dàng và nhanh chóng.

Kiến trúc cho xác thực và phân quyền

Sau khi đã có định danh và giao thức dùng để giao tiếp, câu hỏi tiếp theo là cần trả lời câu hỏi đối tượng với định danh đó có quyền thực hiện 1 hành động, truy cập 1 tài nguyên nào đó hay không. Ở Tiki, bên cạnh các service được xây dựng mới, vẫn còn tồn tại các hệ thống cũ (legacy) chạy song song, thế nên hiện nay Tiki có 2 cách thức tổ chức phân quyền như dưới đây.

Xác thực, phân quyền tại lớp rìa

Theo mô hình tất cả mọi request sẽ được xác thực khi đi qua API Gateway hoặc BFF (Backend For Frontend). BFF chính là lớp service ở rìa (Edge Service) được thiết kế riêng cho từng ứng dụng (ví dụ IOS, Android, Management UI). Chúng ta sẽ đặt xác thực và phân quyền ở lớp rìa này

  • API Gateway sẽ bắt buộc tất cả request sẽ cần gửi kèm token để định danh
  • Nếu token này là JWT (đối với OpenID Connect), Gateway có thể kiểm tra tính hợp lệ của token thông qua chữ ký (signature), thông tin (claim) hoặc đối tượng khởi tạo (issuer)
  • Nết token này là Opaque Token, Gateway có thể phân tích (introspect) token, đổi (exchange) lấy JWT và truyền tiếp vào trong cho các services.
  • API Gateway hoặc BFF kiểm tra các policy xem có hợp lệ hay không thông qua Authorization Server trung tâm.
  • Các microservices không thực hiện lớp xác thực và phân quyền nào, có thể tự do truy cập bên trong vùng nội bộ (internal network).

Mô hình này có điểm tương đồng với kiến trúc Monolithic khi đặt xác thực phân quyền tại 1 số service nhất định, việc xây dựng và bảo trì sẽ tốn chi phí nhỏ hơn, tuy nhiên sẽ để lộ 1 khoảng trống bảo mật rất lớn ở lớp trong do các service có thể tự do truy cập lẫn nhau.

Chúng ta có thể đặt 1 số rule ở góc độ network đối với các service bên trong này tuy nhiên các rule này sẽ tương đối đơn giản và không thể đáp ứng được các nghiệp vụ truy cập dữ liệu lẫn nhau giữa các team/service (mở rộng ra là các công ty nội bộ) độc lập nhau

Xác thực, phân quyền tại các service

Ở mô hình này, mỗi service (trừ 1 số ngoại lệ) khi được thiết kế và xây dựng các giao tiếp APIs (API Interface) mở rộng được và có thể phục vụ cho thế giới bên ngoài. Một service hôm nay được xây dựng cho các nghiệp vụ bên trong nội bộ công ty, nhưng ngày mai có thể sẵn sàng để mở ra cho các đối tác, các lập trình vên ngoài.

Điều này sẽ giúp cho các service/team chủ động được hoàn toàn về các tài nguyên hiện có, tài nguyên đó được cấp cho những đối tượng nào, được truy cập từng phần hay toàn phần…

Để làm được việc này, vai trò rất lớn sẽ nằm ở service IAM (Identity Access Management), IAM nắm giữ các định danh của toàn bộ các đối tượng (user, service, command…) cùng với các bộ luật phân quyền chi tiết cho từng loại tài nguyên.

Việc mỗi service phải tự thực hiện việc xác thực, phân quyền sẽ làm tăng chi phí khi xây dựng các service, bên ngoài các nghiệp vụ chính thì cần thêm lớp middleware để giao tiếp với IAM.

Tuy nhiên các service sẽ có được sự tự chủ hoàn toàn, chủ động về việc cung cấp tài nguyên cho các đối tượng, và tăng tốc phát triển hơn vì nhiều trường hợp client có thể truy cập thẳng tới các service mà không cần phát triển thêm lớp BFF ở giữa.

Access Control

Xây dựng hệ thống luật (rule) hiệu quả không bao giờ là dễ dàng, khi yêu cầu về nghiệp vụ tăng cao kéo theo yêu cầu về phân quyền càng phức tạp. Hãy lấy 1 ví dụ cụ thể để làm rõ, mỗi ứng dùng thông thường sẽ gán quyền cho 1 thành viên cụ thể (ví dụ John được quyền tạo sản phẩm). Mở rộng ra trong 1 hệ thống microservices, đối tượng ở đây có thể là người dùng, service, crontab…

Có 1 vài cách tiếp cận cho việc phân quyền như trên, hãy thử đi qua các cách khác nhau để có nhiều góc nhìn khác nhau.

Access Control List (ACL)

Trong ví dụ trên các bạn có thể thấy 1 ma trận của đối tượng và quyền, nó gần tương đương với cách quản lý file trên Linux (chmod) và phù hợp với những ứng dụng có ít đối tượng. Khi hệ thống lớn lên mô hình này sẽ không thể quản lý nổi bởi ma trận được tạo ra quá lớn và phức tạp. Do vậy và mô hình này không còn phổ biến hiện tại.

Role-Based Access Control (RBAC)

RBAC liên kết đối tượng tới các vai trò (role), và từ vai trò tới các quyền. Chẳng hạn vai trò Administratorcó thể thừa hưởng mọi quyền mà vai trò Manager có, điều này giúp làm giảm độ phức tạp của ma trận quyền, thay vì gán toàn bộ quyền cho Administrator thì chỉ cần cho Administrator thừa hưởng các quyền của Manager.

RBAC rất phổ biến và bạn có thể thấy ở mọi nơi, so với ACL thì RBAC giúp giảm thiểu độ phức tạp khi số lượng đối tượng + quyền tăng cao. Tuy nhiên RBAC chưa thỏa mãn được 1 số trường hợp, ví dụ khi cấp quyền 1 sản phẩm chỉ được sửa bởi người tạo, người dùng nằm trong 1 phòng ban xác định hoặc quyền phân biệt với các người dùng từ nhiều hệ thống (tenant) khác nhau.

Policy-Based Access Control (PBAC)

PBAC được xây dựng dựa trên Attribute Based Access Control (ABAC), qua đó định nghĩa các quyền để diễn đạt một yêu cầu được cho phép hay từ chối. ABAC sử dụng các thuộc tính (attribute) để mô tả cho đối tượng cần được kiểm tra, mỗi thuộc tính là 1 cặp key-value ví dụ Department Marketing. Nhờ đó ABAC có thể giúp phân quyền mịn hơn, phù hợp với nhiều ngữ cảnh (context) và nghiệp vụ (business rules) khác nhau.

PABC được định nghĩa thông qua các policy được viết dưới dạng 1 ngôn ngữ chung XACML (eXtensible Access Control Markup Language). Một policy định nghĩa 4 đối tượng subject, effect, action và resource. Ví dụ john (subject) được allowed(effect) để mà delete(action) product với ID john-leman(resource). Nhìn qua thì nó gần giống với cách định nghĩa 1 ACL.

{
"subjects": ["user:john"],
"effect": "allow",
"actions": ["catalog:delete"]
"resources": ["product:john-leman"],
}

Chúng ta có thể bổ sung subject, action cũng như resource thêm vào policy nếu muốn.

{
"subjects": ["user:john", "user:katy", "user:perry"],
"effect": "allow",
"actions": ["catalog:delete", "catalog:update", "catalog:publish"]
"resources": ["product:john-leman", "product:john-doe"]
}

Bạn có thể thắc mắc thế thì PBAC khác gì ACL, và đây là sự khác biệt

Luật ưu tiên

  • Mặc định nếu không có policy phù hợp, yêu cầu sẽ bị từ chối
  • Nếu không có policy nào deny, có ít nhất một policy allow thì yêu cầu được cho phép
  • Nếu có 1 policy là deny, thì yêu cầu luôn bị từ chối

Regular Expression

Các policy cho phép khai báo sử dụng regular expression, như ở ví dụ này cho phép tất cả người dùng được xem thông tin product.

{
"subjects": ["user:<.*>"],
"effect": "allow",
"actions": ["catalog:read],
"resources": ["product:<.*>"]
}

Điều kiện

Các policy có thể bổ sung các điều kiện để thu hẹp phạm vi của quyền, ví dụ như chỉ áp dụng cho 1 dải IP nhất định, hoặc chỉ cho phép người tạo sản phẩm được sửa sản phẩm đó.

{
"subjects": ["user:ken"],
"actions" : ["catalog:delete", "catalog:create", "catalog:update"],
"effect": "allow",
"resources": ["products:<.*>"],
"conditions": {
"IpAddress": {
"addresses": [
"192.168.0.0/16"
]
}
}
}

Tổng kết

Việc liên tục mở rộng nghiệp vụ và hệ thống đòi hỏi các service phải tự xác thực, qua đó không phân biệt service đó là bên trong (internal) hay bên ngoài (external), giúp các team dễ dàng mở rộng tích hợp với nhau. Việc này đòi hỏi mô hình xác thực chung phải hoạt động ổn định, tối ưu và đáp ứng được hiệu năng cao.

Tham khảo thêm các vị trí tuyển dụng ngành cntt hấp dẫn tại đây

Có thể bạn muốn xem:

  Giao tiếp hiệu quả giữa các Microservice
  Microservices là gì? Speed up Microservices 1: Tác dụng phụ và một số chiến lược cơ bản

Bài viết gốc được đăng tải tại Tiki Engineering

Phân tích 80+ email từ chối ứng tuyển vào vị trí lập trình viên Python và kết quả không ai ngờ đến!

Không lúc này thì lúc khác, mỗi người chúng ta sẽ nhận được vài cái mail từ chối xin việc thôi. Bạn biết đấy, những mail như vậy thường bắt đầu với câu “Cảm ơn bạn đã quan tâm” và kết thúc với những giấc mơ tan vỡ, dập tắt mọi hy vọng đẹp đẽ vốn có. Xin lỗi, có vẻ tôi hơi cực đoan một chút. Tuy nhiên, việc nhận được mail từ chối là một trải nghiệm không dễ dàng gì.

Có thể bạn quan tâm:

  Giao tiếp hiệu quả giữa các Microservice
  Cloud-Native Microservices Với TIBCO: Khám phá dịch vụ bằng cách sử dụng Consul

Tìm việc làm python nhiều vị trí

Tin tôi đi, tôi hiểu rõ lắm! Vì sao ư? Vì tôi đã nhận được hơn 80 email từ chối công việc trong năm qua khi đăng ký làm thực tập sinh. Những con số sau sẽ cho bạn rõ tình trạng của tôi trước khi chúng ta vào vấn đề chính nhé!

Số công ty nộp đơn 234
Hồi âm 93
Từ chối 90
Offer 3

Điều đáng chú ý là, ít nhất là cho chương trình thực tập sinh, việc không nhận được phản hồi thường rất ít gặp. Trên thực tế, hơn 60% công ty mà tôi ứng tuyển đều không thấy phản ứng gì. Theo tôi, điều này đáng lo ngại hơn nhiều so với việc nhận được câu trả lời “không” rõ ràng. Ít nhất trong trường hợp đó, chúng ta sẽ tiếp tục tìm kiếm công ty khác thay vì tiếp tục hy vọng trong vô vọng.

Nhưng thử tưởng tượng xem, sau khi nghe từ “không” đến tận 80 lần, theo tự nhiên, bạn sẽ sinh ra một cảm giác “đối kháng”. Chính sự đối kháng ấy cùng với một chút tò mò là điều khiến tôi nảy ra dự án này. Tóm lại, tôi muốn điều tra xem nguyên nhân gì khiến nhà tuyển dụng gửi những mail từ chối tự động như vậy và chúng khác nhau như thế nào giữa các công ty. Nếu bạn muốn xem thêm về dự án của tôi, vui lòng truy cập vào GitHub repo.

Về phần còn lại của bài viết này, tôi sẽ chia sẻ cách tôi phân tích các email từ chối ra sao.

Bước 1: Thu thập dữ liệu

Trước tiên, hãy lấy dữ liệu! Khi truy vấn hộp thư đến Gmail của tôi, tôi đã tìm thấy hơn 1.000 email chứa các từ khóa “thực tập” và “ứng dụng”. Điều này khiến mọi việc trở nên khó khăn một chút từ góc nhìn tự động hóa.

Tôi đã ngồi phân loại các mail từ chối của mỗi công ty. Thậm chí tôi còn tạo một nhãn trong Gmail có tên “Job Rejection”. Xong xuôi, đã đến lúc tận dụng sức mạnh của Python. Tôi đã đăng nhập bằng imaplib và xóa bỏ các phần nổi của email. Để phân tích nội dung, tôi đã mã hóa văn bản bằng nltk  và xóa bất kỳ dấu câu và stop words nào có trong mail.

Xem thêm các vị trí lập trình viên python

Bước 2: Những cụm từ phổ biến

Bạn có bao giờ thắc mắc rằng tại sao các mail từ chối đều sử dụng một ngôn ngữ chung không? Tôi thường gặp rất nhiều cụm từ giống nhau, dường như được sử dụng từ công ty này đến công ty khác. Một số công ty có cách dùng từ ngữ rất linh hoạt và sáng tạo, nhưng rất hiếm khi gặp những trường hợp như vậy.

Hãy xem các từ và cụm từ phổ biến nhất được sử dụng cho những email này.

Như bạn có thể thấy ở trên, các nhà tuyển dụng lựa chọn từ “application” và “your” nhiều hơn hẳn những từ khác. Không cần phải bỏ ra nhiều công sức, bạn đã có thể lọc ra các mail có chủ đề như vậy:

  • Thank you for your interest.
  • Update on your [Insert role] application.

Những từ này thường là điềm báo cho những tin xấu đấy!

Video: Nhận dạng âm thanh: phân biệt giọng nam, nữ và vùng miền (Bắc Trung Nam)

 

Bước 3: Khung thời gian

Hãy tiến hành thêm một bước nữa là tổng hợp thời gian các email từ chối được gửi đi. Điều này khiến tôi nhớ đến một lời khuyên lâu đời: đừng nên sa thải nhân viên vào thứ Sáu, vì chúng ta không biết cuối tuần họ sẽ làm gì. Mặc dù chuyện này không quan trọng bằng chuyện sa thải nhân viên, nhưng vẫn rất thú vị khi thử nghĩ xem nhà tuyển dụng chọn thời gian nào để gửi “hung tin” đến ứng viên.

Sau đây là phân tích của tôi dựa trên các ngày trong tuần:

Có vẻ “Hump Day” (thứ Tư theo tư duy của người Mỹ) ngày thứ tư đen tối. Thứ năm ở vị trí nhì bảng, các ngày còn lại có số lượng khá nhất quán. Cuối tuần là khoảng thời gian nghỉ ngơi, tuy nhiên vẫn có một số nhà tuyển dụng gửi mail vào thứ Bảy?! Xấu hổ…

Đối với giờ trong ngày, độ phân bố nhìn khá đều theo như dự đoán của chúng ta. Có vẻ như số lần từ chối dao động vào lúc 9 giờ sáng đến giữa trưa. Thông thường đây là giờ bắt đầu làm việc của các công ty theo múi giờ EST và PST. Chỉ có một lần từ chối duy nhất là sau 5 giờ chiều. Và công ty đó không ai khác ngoài P&G.

Đến đây, phần phân tích dữ liệu của thôi đã hoàn tất.

Tổng kết

Dư án phân tích email từ chối nhận việc đối với tôi khá thú vị. Hơn thế nữa, nó cho tôi kinh nghiệm thực tiễn trong quá trình tìm viêc sau này cũng như cách ứng xử khi nhận được email từ chối. Không nhiều người có đủ coi trọng kỹ năng đối diện với thất bại đâu.

“Tôi đã thất bại nhiều và nhiều lần trong cuộc sống của tôi … và đó là lý do tại sao tôi thành công” – Michael Jordan

Sau hơn 80 email từ chối, rõ ràng, bạn sẽ quen với khái niệm thất bại. Tôi thấy mình ngày càng ít do dự khi nộp đơn vào các vị trí tại các công ty uy tín. Điều này cho phép tôi có được cơ hội mà ban đầu tôi không nghĩ nó nằm trong tầm tay của mình.

Nếu bạn đang đọc bài này, hãy tận dụng những cơ hội đó, tiếp cận nhà tuyển dụng thêm một chút, và hãy tự tin đối diện với thất bại. Rồi lại đứng lên. Lại cố gắng. Chỉ qua quá trình dài cố gắng, chúng ta mới có được thành công. Và khi bạn đã chạm đến đỉnh vinh quang, đừng quên nhìn lại những thất bại đã qua và tự nói với lòng mình rằng:

Thank you for your interest.

TopDev via Medium

Tìm việc IT lương cao, đãi ngộ tốt trên TopDev ngay!

ADVN và cơ hội onsite Singapore dành cho “chiến binh” Ruby on Rails

ADVN và cơ hội onsite Singapore

Với vị thế hiện tại, ADVN đang dần chiếm ưu thế trong mối quan hệ hợp tác với các thương hiệu công nghệ hàng đầu. Nhằm mục đích tiến xa hơn trong lĩnh vực phát triển các dự án outsource, ADVN vẫn đang không ngừng chiêu mộ các tài năng IT với mức lương vô cùng hấp dẫn.

Sự tín nhiệm bởi các doanh nghiệp hàng đầu về công nghệ dành cho ADVN!

Từ năm 2016, ADVN trở thành nhà phân phối chính thức các thiết bị tin học mang thương hiệu tầm cỡ thế giới tại Việt Nam. ADVN đang ngày càng khẳng định vị thế của mình bởi sự tín nhiệm từ các thương hiệu công nghệ hàng đầu như MSI, Sony, Hitachi, InFocus, Prolink… Đây được xem là bước đà phát triển cho ADVN trong mối quan hệ hợp tác với các doanh nghiệp công nghệ ở thị trường quốc tế.

Hiện nay, ADVN đang tập trung sang các dự án outsource, thương mại điện tử và quảng cáo. Với mong muốn mang đến những sản phẩm, dịch vụ chất lượng kết hợp công nghệ tiên tiến nhất, ADVN đang ra sức săn đón các tài năng IT để cùng đồng hành phát triển.

Đội ngũ IT tại ADVN hiện đang tìm kiếm một mảnh ghép mới với vai trò là một Ruby on Rails Developer, là người thích phát triển phần mềm web, thương mại điện tử với việc tích hợp vào các ứng dụng mobile. Tại đây, ứng viên sẽ được phát triển năng lực của mình, phối hợp làm việc cùng với các lập trình viên khác, Project Manager và đội ngũ Infrastructure.

ADVN và con số offer đến $1,500 dành cho Ruby on Rails Developer

Nếu bạn muốn hợp tác cùng đội ngũ ADVN để nâng tầm giá trị kinh nghiệm và chuyên môn của mình, hãy gửi CV ứng tuyển ngay hôm nay. ADVN sẵn sàng tạo cho bạn một điều kiện không gian làm việc tốt nhất, đi kèm với mức lương thưởng và đãi ngộ xứng đáng:

  • Mức lương hấp dẫn $800 – $1,500;
  • Review lương hàng năm, nâng cao chất lượng sống;
  • Được đảm bảo mọi quyền lợi các chế độ (ngày phép năm, gói bảo hiểm) đầy đủ theo luật Việt Nam;
  • Cơ hội công tác tại Singapore tích lũy kinh nghiệm từ bạn bè quốc tế, nâng trình giao tiếp tiếng Anh;
  • Được thử sức mình với các dự án đầy tính thử thách, cải thiện và trau dồi kỹ năng làm việc theo nhóm;
  • Cơ hội phát triển mạnh mẽ với sự hướng dẫn tận tình, giúp đỡ trong suốt quá trình làm việc.

Nắm tay nhau cùng tiến – Nâng khả năng tác chiến

Tham khảo tuyển dụng ruby on rails lương cao trên TopDev

Mô tả công việc:

– Phát triển trang CMS website, e-commerce và ứng dụng mobile;
– Phân tích các yêu cầu của khách hàng để phát triển các yêu cầu phần mềm kỹ thuật và tính năng (outsourcing);
– Phối hợp chặt chẽ với Project Manager để đưa ra ý tưởng và giải pháp phát triển các tính năng mới;
– Đảm bảo tiến độ công việc được giao;
– Tham gia vào các dự án sản phẩm của ADVN.

Yêu cầu:

– Bằng cấp / Văn bằng Khoa học Máy tính, Công nghệ thông tin hoặc các ngành liên quan;
– Có ít nhất 2 năm kinh nghiệm vị trí Ruby on Rails Developer;
– Có kiến thức về web Front-end như HTML, CSS, JavaScript & jQuery, API RESTful;
– Có kiến thức về Angular JS, React JS, máy chủ Linux / CentOS là một điểm cộng;
– Kinh nghiệm với hệ thống cơ sở dữ liệu MySQL;
– Khả năng giám sát và cố vấn cho các junior engineers;
– Khả năng đọc và nói tiếng Anh;
– Khả năng tư duy logic;
– Tinh thần làm việc nhóm tốt.

Nhiều tin tuyển dụng IT lương cao trên TopDev, đăng ký ngay!

5 công nghệ huyền thoại sẽ không bao giờ lỗi thời!

5 công nghệ huyền thoại sẽ không bao giờ lỗi thời

Công nghệ đang thay đổi với mức độ chóng mặt mỗi ngày. Những ngôn ngữ mới liên tục được ra đời như Ruby, Hadoop hay những công nghệ mới nhất cloud đang ngày càng phổ biến hơn. Tuy nhiên, những ngôn ngữ và kỹ năng cơ bản vẫn đóng vai trò như những bộ khung sườn vững chắc giúp mọi thứ vận hành suôn sẻ. Sau đây là 5 loại công nghệ vẫn đóng vai trò lớn cho đến tận thời điểm này.

COBOL

123

“Rất nhiều người dùng cuối hiện vẫn đang tương tác với các hệ thống được xây dựng bằng COBOL.” – Ed Airey, Product Marketing Director cho COBOL solution tại MicroFocus cho biết.

Các dịch vụ như Ngân hàng, bảo hiểm, tàu lửa hay hàng không là những dịch vụ mà khách hàng tương tác với COBOL nhiều nhất, vì ngôn ngữ này vượt trội ở khả năng tính toán và quản lý lưu lượng thông tin dữ liệu lớn.

“COBOL vượt trội hơn các ngôn ngữ khác ở khoảng xử lý các khối lượng dữ liệu rất lớn, tính năng này được gọi là batch processing,” Airey cho biết. Các hệ thống ngân hàng, công ty tín dụng, và các công ty IRS đều sử dụng hệ thống được lập trình bởi COBOL để xử lý một lượng lớn các giao dịch trong cùng một thời điểm. COBOL còn được sử dụng ở các hệ thống kiểm tra lý lịch di trú và nhập cảnh. Quá trình này còn giúp khoanh vùng các đối tượng cần được theo dõi.

TOP các vị trí tuyển dụng lập trình Cobol

Mainframes

mainframe

“Hầu hết các hệ thống máy tính quy mô lớn đã được phát triển trong những năm 1960. Với kết cấu đồ sộ, chúng vẫn được phát triển tiếp tục đến tận bây giờ. Sự ổn định của nó cho phép mainframes (hay big iron)  có thể chạy ổn định mà không bị gián đoạn trong suốt nhiều thập kỷ. Những ngành như Ngân Hàng, bán lẻ, dịch vụ tài chính, logistics, và sản xuất phụ thuộc khá nhiều vào công nghệ mainframe.” – Craig O’Malley, CEO của mainframe solutions Compuware cho biết.

“Công nghệ Mainframe chắc chắn vẫn sẽ đóng vai trò chủ chốt cho rất nhiều hệ thống back-end. Đây là giải pháp công nghệ tốt nhất, nhanh nhất và kinh tế nhất so với những gì mà nó làm được. Nó không chỉ được dùng cho batch processing, hay tăng tốc độ xử lý dữ liệu, mà nó chính là một giải pháp tối ưu để xử lý lưu lượng data cực lớn.”

Ngôn ngữ C

124

C được phát triển bởi Dennis Ritchie ở Bell Labs vào khoảng những năm 1969 và 1973. C được ví như một trong những ngôn ngữ lập trình được sử dụng nhiều nhất. Trên thực tế, rất nhiều ngôn ngữ “hậu bối” như C++, Python, Perl, Java và PHP đã mượn rất nhiều tính năng cơ bản từ C.

Ngôn ngữ này được dùng rất rộng rãi ở các hệ thống lập trình kể cả hệ điều hành hoặc các hệ thống nhúng, chưa kể đến các hệ thông siêu máy tính lớn cũng đang dựa vào ngôn ngữ này. Và đương nhiên C còn là nền tảng của khá nhiều hệ điều hành trong đó bao gồm cả Unix.

Video IoT and AI Thinking Linking Things Age of VUI

Fortran

125

Là ngôn ngữ được biết đến với tên gọi Formula Translation, là một ngôn ngữ được dùng cho các hệ thống máy tính lớn (mainframe) của IBM từ những năm 1950s. Nó được ví như “tiếng mẹ đẻ của khoa học máy tính”. FORTRAN được phát triển bởi Big Blue để đẩy nhanh tốc độ xử lý thông tin khoa học kỹ thuật trên các siêu máy tính.

Hiện nay, nó được dùng nhiều ở những lĩnh vực như dự báo thời tiết, động lực học, vật lý máy tính và những lĩnh vực đòi hỏi tốc độ xử lý cao.

Java

126

Về mặt kỹ thuật mà nói, Java không phải là một công nghệ siêu việt, nhưng nó sẽ chính thức ăn mừng sinh nhật thứ 20 của mình vào năm nay. Java được phát triển bởi James Gosling tại Sun Microsystem vào năm 1995. Nó được thiết kế với một mục đính giúp cho các nhà phát triển có thể “viết code ở một nơi, và chạy được ở nhiều nơi” (write once, run anywhere), mà không phải dịch lại thông qua một nền tảng nào khác.

Dù là một ngôn ngữ “trẻ tuổi” nhưng Java vẫn hiện đang nằm thuộc top những ngôn ngữ được cộng đồng sử dụng nhiều nhất, được đánh giá bởi cộng đồng Tiobe Software’s Programming Community Index. Với khoảng 9 triệu nhà phát triển, Java nằm vào top những ngôn ngữ được nhiều người dùng nhất, đa phần được dùng để xây dựng ứng dụng client-server Web.

TOp các việc làm cho lập trình Java

TopDev

Tôi không phải là lập trình viên thực sự?

Tôi không phải là lập trình viên thực sự

Hiện tại mình là một system engineer, nhưng mà ban đầu mình không apply vào vị trí này: Bốn năm trước mình bắt đầu bước vào công ty với vị trí là lập trình viên support chính thức đầu tiên.

Dù đã làm ở vị trí khác nhưng mình vẫn thích gần gũi với team support. Có cậu em làm support engineer trong team hiện tại thường thích gửi cho mình những code snippet mà cậu ấy viết để trao đổi về nó hoặc khoe về mấy điều hay ho cậu ấy học được. Và một ngày nọ cậu ấy gửi cho mình cái này:

“Một lập trình viên thực sự là [….]?, bữa giờ làm việc em thấy mình không phải là lập trình viên đúng nghĩa cho lắm”

Image result for exhausted meme gif

 

Công việc của mình thật ra khá bận rộn và mình cũng không phải là người trả lời tin nhắn ngay lập tức. Thế nên mình đã quay lại sau bữa trưa và trả lời như sau:

“Đừng bao giờ nói rằng mình không phải là lập trình viên

vì mày là lập trình viên mà”

cậu ấy đáp lại rằng “anh biết ý em là gì mà. em chỉ toàn “ráp” mọi thứ lại với nhau”

Đương nhiên là mình biết ý cậu ấy là gì. Mình luôn muốn trở thành một lập trình viên – Ngay từ khi còn nhỏ mình đã thích vọc mấy cái máy tính cũ của ông già, script trên những máy tính vẽ đồ thị trên trường, đòi cuốn dạy code C++ mua ở nhà sách cũ. Mình cũng khá thân với các giáo viên dạy trên trường, thậm chí tôi còn xin một một job code web dạo bán thời gian. Sau khi tốt nghiệp, mình tiếp tục học tiếp lên đại học. Nghe có vẻ tương lai rộng mở đúng không?

Có thể bạn muốn xem:

  Tự học một cách chủ động từ dự án và đồng nghiệp
  6 điều tôi vỡ lẻ khi tự học code (P1)

Tuy nhiên, mình cũng gãy gánh bỏ học đại học giữa chừng và mất hơn 10 năm liên tục cố gắng rồi thất bại rồi lại tiếp tục cố gắng để trở thành một lập trình viên / developer chuyên nghiệp. Khi cơ hội việc làm ở công ty A tới (Giấu tên để sếp khỏi biết), mình đã chuyển từ support sang software QA, và nghĩ rằng mình đang ngày càng tới gần “công việc mơ ước” của mình. Khi làm việc tại A (sau đó là Risk I/O) – một startup với những con người thú vị, thông minh và tuyệt vời, mình thật sự quá ngán và không muốn quay lại vị trí support nữa. Mình vẫn muốn trở thành một lập trình viên thật sự, và mình đã dành nhiều năm làm việc bên cạnh các lập trình viên thực sự mà không thể làm chính xác như những gì họ làm.

Công ty A là một công ty rất khác biệt. Mình được truy cập vào GitHub. Mình có cả dev instance của riêng mình. Mình được dạy cách create và merge các PR và xem code của mình được triển khai để sản xuất. Đúng là Mình đang ở vị trí support, và mình vẫn phải thực hiện các cuộc gọi để hỗ trợ kỹ thuật, nhưng cảm ơn trời, cuối cùng mình cũng được học và viết Ruby. Thậm chí sau này có một engineer mới vô nói với mình rằng “A nh không biết chú là support đó, Anh nghĩ chú cũng rất có khiếu code giống mấy anh em ở đây đó nha”.

Sau đó mình có ít thời gian để vọc code hơn và ngập đầu trong công việc support bởi vì client của chúng mình phát triển quy mô và độ phức tạp. Rồi thì công ty cũng bắt đầu phát triển đội ngũ support và thuê thêm một số support engineer, những người sẽ thực hiện công việc đó tốt hơn mình. Đây cũng là một cơ hội để mình chuyển sang những platform/system/operation side của công ty.

Rồi mình đổi sang công ty B (lại giấu tên), mình được làm công việc backend, infrastructure. Mình làm việc khá hợp với Red Hat, Ubuntu và những thứ khác trong nhiều năm, nhưng mình vẫn chưa làm tốt trong việc quản trị hệ thống Linux. Sếp của mình cũng khá nice VÀ đã cho mình thêm cơ hội, và hy vọng Mình sẽ phát triển đến trình độ system engineer mà họ cần.

Đó là một vài năm trước, còn bây giờ mình cảm thấy mình đã làm tốt công việc của một system engineer – nhưng mình có phải là một lập trình viên thực sự không?

Khi cậu em support engineer kia nói: “Em không phải là một lập trình viên thực sự,  em chỉ gắn mọi thứ lại với nhau”. Mình đã trả lời lại rằng:

“Đó chính xác là việc của anh em mình”

“Chú quá biết rồi còn gì”

Đó không phải chính xác những gì một lập trình viên làm ư? Năm ngoái có một bài viết tuyệt vời nói rằng “Phần lớn Software Engineering trong năm 2018 là như một hệ thống cấp nước” và nó đúng ở quy mô lớn như khi đơn giản là lấy các string từ các log file.

Mình biết rất nhiều người trong ngành công nghệ bị Hội chứng tạm gọi là Kẻ mạo danh (Imposter Syndrome), và thật sẽ rất khó cho những support engineer – những người không có chữ “developer” hay “programmer” trong tên nghề nghiệp. Mình dành hầu như cả sự nghiệp của mình với những con chip, nhưng nhờ những con người tuyệt vời xung quanh mà mình đã may mắn rũ bỏ được tâm lý đó.

Các bạn nếu có cảm thấy giống mình, thì hãy tự tin lên nha, vì lập trình viên cũng có nhiều hình thái. Mình tin tất cả chúng ta đều là lập trình viên thực thụ.

Tìm việc IT lương cao, đãi ngộ tốt trên TopDev ngay!